Introduction

In one of the greatest cities in the world, NYC, we have an abundant amount of options to choose from when it comes to where we eat, whether we are looking for fine dining, fast food, or something in between. The cuisine choices seem just as endless. Is there a good way to help choose a restaurant to eat in or a general location? Although not an easy task, one thing we can all agree on is that nobody wants to eat in a restaurant ridden with health violations. In our project, we will explore and analyze NYC’s restaurant inspection results from 2013-2017 of the five boroughs.

We are interested in answering:

  1. Which cuisines have the least and most violations along with their associated score?

  2. What parts of NYC have the least and most violations?

  3. We hypothesize that location is highly associated with inspection grade and so we will be searching for patterns between these variables.

  4. We are also interested in seeing how inspection grade and score changes over time (years) based upon cuisine and location.

  5. A description of violations is also provided in the data and we would like to better understand the common causes of those violations based upon the description.

  6. Is there a pattern/trend in violations/inspection score/grade based upon restaurant chains?

We found this data set by exploring NYC open data sets. Our love for food and health made this a great option. One of our team members who recently moved to NYC is highly selective about the food she eats due to her rare health condition and hence, having the kinds of questions mentioned above answered can prove to be a great asset to both her and others suffering from health conditions or who may just be picky eaters! The data can be found and downloaded here: https://data.cityofnewyork.us/Health/DOHMH-New-York-City-Restaurant-Inspection-Results/xx67-kt59

To download, click the export button on the right -> Download as -> choose your format. For this assignement we used the CSV format.

Team

Team Members:
  • Jonathan Galsurkar
  • Lakshya Garg
Task Distribution

Get table format for this

library(knitr)
TaskDistribution <- read.csv(file="TaskDistribution.csv", 
                           header=TRUE, sep=",",as.is=TRUE)
kable(TaskDistribution )
S.No Task Assignee
1 Cleaning up the data Jonathan Galsurkar
2 identifying missing data-including understanding what missing values mean Lakshya Garg
3 Preparing executive summary Jonathan Galsurkar
4 Questions 1, 2, 5 Jonathan Galsurkar
5 Questions 3, 4, 6 Lakshya Garg
6 Pushing the data analysis to GITHUB website Both
7 Render plots in R Both
8 Derive data for plotting in Tableau Both
9 Creating maps in Tableau Lakshya Garg
10 Report Writeup Both
11 Visualization tidyup Both

Analysis of Data Quality

To begin our analysis of data quality, let’s load our data.

ViolationsData <- read.csv(file="inspection.csv", header=TRUE, sep=",", as.is=TRUE)

Dataset Description

DatasetDescription <- read.csv(file="DescriptionDataset.csv", header=TRUE, sep=",", as.is=TRUE)
kable(DatasetDescription )
Field Description
CAMIS Record ID
DBA Name of the resteraunt
BORO Borough(Bronx,Brooklyn,Queens,Manhattan,Staten Islands)
BUILDING Building address of resteraunt
STREET Street address of resteraunt
ZIPCODE ZIPCODE of resteraunt location
PHONE Phone number fo resteraunt
CUISINE DESCRIPTION Cusine type
INSPECTION DATE Date of inspection-used to derive inspection year
ACTION Action reported by inspector
VIOLATION CODE Violation code as per blue book
VIOLATION DESCRIPTION Violation description
CRITICAL FLAG Criticality flag of violation reported
SCORE Score awarded
GRADE Grade awarder after inspection-can be missing
GRADE DATE Date of grade award
RECORD DATE Date the record entered the inspection results
INSPECTION TYPE Type of inpection.initial/re-inspection

In order to more easily work with our data let’s ensure that every date is in a date format rather than a string and every string is a factor variable.

library(dplyr)
library(tidyr)
##Date conversion
ViolationsData <- ViolationsData %>%
  mutate(INSPECTION.DATE= as.Date(INSPECTION.DATE, format= "%m/%d/%Y"))%>%
  mutate(GRADE.DATE= as.Date(GRADE.DATE, format= "%m/%d/%Y"))%>%
  mutate(RECORD.DATE= as.Date(RECORD.DATE, format= "%m/%d/%Y"))
##Factor conversion
ViolationsData <- ViolationsData %>%
  mutate(CUISINE.DESCRIPTION= as.factor(CUISINE.DESCRIPTION))%>%
  mutate(BORO= as.factor(BORO))%>%
  mutate(VIOLATION.CODE= as.factor(VIOLATION.CODE))%>%
  mutate(CRITICAL.FLAG= as.factor(CRITICAL.FLAG))%>%
  mutate(GRADE= as.factor(GRADE))%>%
  mutate(INSPECTION.TYPE= as.factor(INSPECTION.TYPE))
summarize <- dplyr::summarize
mutate <- dplyr::mutate

Many of the questions we are interested in answering involve trends across restuarant locations. We first checked to see the number of restuarant inspections by borough. From the plot below, we noticed that there were a number of inspections in which the Borough information was missing and won’t help our analysis.

library(ggplot2)
boroughPlot <- ggplot(ViolationsData, aes(BORO, fill=BORO))
boroughPlot + geom_bar()+ theme(legend.position="none") +
  ggtitle('Inspection Count by Borough') + 
  labs(x = "Borough", y ="Number of Inspections")

Another main feature of our data set is the inspection year since we wish to explore patters in inspection grades/scores over the years. From the plot below, we noticed that there is almost no inspection data before 2013 and surprisingly more inspection data in 1900 than 2012 and 2011. We decided to only work with data from 2013 and up. It is important to note that the number of inspections for 2017 is low since the 2017 data is only available from January - March.

violationYearsPlot <- ggplot(ViolationsData, aes(factor(as.numeric(format(INSPECTION.DATE, '%Y'))), fill="Red"))
violationYearsPlot + geom_bar()+coord_flip()+ theme(legend.position="none") +xlab("Inspection Year")+ylab("Inspection Counts") + ggtitle("Inspection Count by Year")

Another crucial feature of our data set is grade a restaurant received after inspection. We decided to plot a stacked bar chart for to see the count of each type of grade for every cuisine. We used a stacked bar chart because we wanted to quickly assess the magnitude missing without taking up extra room. From the plot below, it was shocking to see that we generally had more missing grades than grades. This was true regardlss of cuisine. It was also interesting that the grades were purely missing and not categorized as “Not Yet Graded”.

ggplot(ViolationsData, aes(CUISINE.DESCRIPTION, fill = GRADE)) + geom_bar() + 
  coord_flip() + ggtitle("Stacked Bar Chart of Grade Distributions Across Cuisines") +
  xlab('Cuisine') + ylab('Count') +
  theme(plot.title = element_text(size = 70, face = "bold"), 
        axis.text.x = element_text(colour="grey20",size=60,angle=0,hjust=.5, 
                                   vjust=.5,face="plain"), 
        axis.text.y = element_text(colour="grey20",size=30,angle=0,hjust=1,
                                   vjust=0,face="plain"),  
        axis.title.x = element_text(colour="grey20",size=60,angle=0,hjust=.5,
                                    vjust=0,face="plain"),
        axis.title.y = element_text(colour="grey20",size=50,angle=90,hjust=.5,
                                    vjust=.5,face="plain"),
        legend.title = element_text(size=40),
        legend.text=element_text(size=40), legend.key.size = unit(1, 'in'))

To stay on the topic of grades, after researching the letter grading program, we found the following information:

  • Blank Grade (Red): The following are scored but not graded. For ex. Initial inspections that result in a score of 14 points or higher, monitoring inspections at a restaurant that has performed very poorly on its re-inspection. The Health Department may continue to inspect the restaurant roughly once a month until it scores below 28 or the Department closes it for serious and persistent violations, inspections at new restaurants not yet open to the public, an inspection at a restaurant seeking to reopen after the Department closed it, some inspections in response to complaints.

  • A score of less than 14 points on either initial or re-inspection results in an “A” grade

  • On re-inspection, a score of 14-27 points means a restaurant receives both a “B” grade and a “Grade Pending” card.

  • On re-inspection, a score of 28 or more points means a restaurant receives both a “C” grade and a “Grade Pending” card.

  • Both Z and P represent grade pending, however P represents a Grade Pending issued on re-opening following an initial inspection that resulted in a closure.

We also discovered that not every inspection is “gradable”. Gradable inspections have the following properties:

  • INSPECTION TYPE in (Cycle Inspection/Initial Inspection, Cycle Inspection/Re-Inspection, Pre-Permit (Operational)/Initial Inspection, Pre-Permit (Operational)/Re-Inspection)

  • ACTION in (Violations were cited in the following area(s), No violations were recorded at the time of this inspection, Establishment Closed by DOHMH)

  • INSPECTION DATE > July 26, 2010

This can probably explain a fair amount of the missing grade data observed in our plot.

According to the ABOUT the data set page: The SCORE and GRADE fields may be inconsistent with each other because of limitations or errors in the data systems. That is to say, scores of 0-13, 14-27 and 28+ are not always accompanied by A, B and C grades, respectively, when they should be. There may also be cases where a grade card was given out but a record of that grade issuance is missing from the data system, and therefore missing from this dataset, even though the SCORE field is populated. Note that when initial inspections are adjudicated down to the A range, the absence of an accompanying grade associated with that inspection is correct, because the grade would not be assigned until the re-inspection is performed.

To gain some final insight on the data quality, we decided to plot the relationshp between the number of missing scores by grade and by whether or not a violation was reported. We transformed the actions taken into three categories: 1. No violations were recorded at the time of this inspection to No Violation 2. Any action reported to Violation reported 3. Missing actions to NA We then counted if the score was provided or not.

library(plyr)
score_grade <- ViolationsData[ -c(1:9, 11:13, 16:18) ]
score_grade[score_grade == ''] <- NA
score_grade_combos <- score_grade  %>% mutate(missing_score = ifelse(is.na(SCORE), "yes", "no"))
score_grade_missing <- count(score_grade_combos, c('GRADE', 'missing_score', 'ACTION'))
score_grade_missing <- score_grade_missing %>% 
  mutate(violation = ifelse(ACTION == 'No violations were recorded at the time of this inspection.', 
                                          "No violation", 
                            ifelse(is.na(score_grade_missing$ACTION), NA, "Violation Reported"))) %>%
  select(-c(ACTION))
ggplot(score_grade_missing, aes(x = GRADE, y = log(freq), fill = missing_score)) + 
  geom_bar(stat = 'identity', position = 'dodge') + facet_wrap(~violation) + 
  ggtitle("Grade and missing score combinations by violation report") +
  labs(x = "Grade", y = "Log(Frequency)")

Other interesting insights by exploring data manually are:

  1. Grades of NA reported high frequency of score in both No violation and violation reported category.

  2. At first, it appears that high scores are related to low grades or needs grading but then we find restaurants with a grade of A that has the same score as a restaurant with a grade of C.

  3. Another insight by just looking at the data is we surprisingly saw that restaurants with a critical flag still receive grades of A.

Executive Summary

NYC puts a lot of time and money into inspecting restuarants. Why would they go through all this trouble? To ensure quality meals and satisfaction of NYC residents of course! It is important for restaurants and establishment that meet at least the minimum requirements of health and safety regulations in order to promote less food sick residents and a cleaner NYC.

How can we tell if these inspections are actually working to promote restuarants to meet regulations? Let’s take a look at the proportion of scores that were graded an A in 2013 and the proportion of scores that were graded an A in 2016. Let’s view this information by location so that you know what neighborhood to choose when you’re craving a restaurant.

The darker the shade of red, the higher proportion of A grades. It is clear that over the last 3 years, the proportion of restuarants with a grade of an A has inscreased. NYC inspections must be working to improve the quality of the restuarants we eat in! In 2013, it seems that the there were only a few neighborhoods with an extremely high proportion of A grades. Only 3 areas in Staten Island, 1 in Long Island, a few in Brooklyn, Manhattan, and the Bronx. In 2016 on the other hand, the proportion of A grades is very high almost regardless of neighborhood. Keep it up NYC restuarant inspections!

To see this from another angle, let’s look at the average scores per neighborhood in 2013 and 2016. Average scores were binned in the legend such that:

Just look how much lighter the color of each neighborhood got! Lighter colors mean a lower average score, which correlates to less violations and healthier establishments!

Well now we know what neighborhoods to be in for establishments with low scores and A grades, but what are the average scores based upon cuisine?

It is interesting that Not Listed/Not Applicable and Other made it to the top 10 but there you have it, the best and worst cuisines based upon their average scores.

Main Analysis

After analyzing the quality of the data set, we got rid of data in which the Borough is missing and the year is before 2013.

ViolationsData <- ViolationsData %>% filter (BORO != "Missing")
ViolationsData <- ViolationsData %>% filter(
  as.numeric(format(INSPECTION.DATE , '%Y')) > 2012)

Now we can get a better picture of the total number of inspections by year and borough.

boroughPlot <- ggplot(ViolationsData, aes(BORO,fill=BORO))
boroughPlot + geom_bar() + theme(legend.position="none") + 
  facet_wrap(~factor(as.numeric( format(INSPECTION.DATE , '%Y')))) + 
  ggtitle("Total Inspections from 2013-2017 in each Borough") +
  xlab("Borough") + ylab("Number of Inspections") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

After 2013, there seems to be a consistent amount of inspections across the years. The number of inspections by borough also seems to make sense since we expect Manhattan to have the largest number of restuarants.

Next, we took a look at the grade distribution by borough.

inspection_grades <- ViolationsData %>% select(-CAMIS, -DBA, -BUILDING, -STREET, -ZIPCODE, -PHONE, -ACTION, -VIOLATION.CODE, -VIOLATION.DESCRIPTION, -CRITICAL.FLAG, -SCORE, -GRADE.DATE, -RECORD.DATE, -INSPECTION.TYPE,-CUISINE.DESCRIPTION)
inspection_grades_woyear<-inspection_grades %>% select(-INSPECTION.DATE)
inspection_grades_woyear <- inspection_grades_woyear %>% gather(key, value, -BORO) %>% group_by(BORO, key, value) %>% tally %>% spread(value, n, fill = 0)
names(inspection_grades_woyear)[names(inspection_grades_woyear)=="key"] <- "grade"
names(inspection_grades_woyear)[names(inspection_grades_woyear)==""] <- "`Unknown"
inspection_grades_woyear <- inspection_grades_woyear %>% gather(key, value, -BORO,-grade)
RelFreq<-function(m){
   ((m )/sum(m))
}
inspection_grades_woyear<-inspection_grades_woyear %>% group_by(BORO,grade) %>%
    mutate(percentage = (value/sum(value))*100)
ggplot(inspection_grades_woyear, aes(BORO, percentage ,fill= key)) + 
  geom_bar(stat="identity", position = "dodge") +
  ggtitle('%Grade Distribution across boroughs') + 
  labs(x = "Borough", y ="Percentage") + guides(fill=guide_legend(title="Grade"))  + theme(plot.title = element_text(size = 25, face = "bold"))

We initially hypothesized that we would see a pattern in grade distribution by borough, however, the plot shows us that grade distributions are almost the same regardless of borough.

inspection_grades_year<-inspection_grades%>% mutate(year=factor(as.numeric(format(INSPECTION.DATE , '%Y'))))
inspection_grades_year<-inspection_grades_year%>% select(-INSPECTION.DATE)
 inspection_grades_year <- inspection_grades_year %>% gather(key, value, -BORO,-year) %>% group_by(BORO,year, key, value) %>% 
   tally %>% spread(value, n, fill = 0)# %>% gather(blah, -BORO, -key) #summarize(aprop = A/(A+B+C+))
names(inspection_grades_year)[names(inspection_grades_year)=="key"] <- "grade"
names(inspection_grades_year)[names(inspection_grades_year)==""] <- "`Unknown"
inspection_grades_year <- inspection_grades_year %>% gather(key, value, -BORO,-grade,-year)
RelFreq<-function(m){
   ((m )/sum(m))
 }
inspection_grades_year<-inspection_grades_year %>% group_by(BORO,grade,year) %>%
    mutate(percentage = (value/sum(value))*100)
ggplot(inspection_grades_year, aes(BORO, percentage ,fill= key)) + 
  geom_bar(stat="identity", position = "dodge") +facet_wrap(~year,nrow=2,ncol=3)+
  guides(fill=guide_legend(title="Grade"))+
  ggtitle("%Grade Distribution by Borough and Year")+ theme(plot.title = element_text(size = 25, face = "bold"))

Adding year to this analysis showed us an increase in the proportian of As in Staten Island in 2015 but seemed consistent throughout the rest of the plot.

We decided that borough may be too general and thus looked at grade distribution by zip code across the various years. We used a heat map to do so.

library(viridis)
nonYearDataForHeatMap<-ViolationsData %>% select(GRADE,ZIPCODE)
nonYearDataForHeatMap <- nonYearDataForHeatMap %>% group_by(GRADE,ZIPCODE) %>% tally 
RelFreq<-function(m){
   ((m )/sum(m))
}
nonYearDataForHeatMap<-  nonYearDataForHeatMap %>% group_by(ZIPCODE)%>%
    mutate(percentage = (n/sum(n))*100)
ggplot(nonYearDataForHeatMap, aes(GRADE, 
                        ZIPCODE, fill = percentage)) +
  geom_tile() +
  scale_fill_viridis() +
  ggtitle("Percentage Grade Distribution by Zipcode\n ")+ylab("Zip Code") + theme(plot.title = element_text(size = 35), 
        axis.text.x = element_text(colour="grey20",size=15,angle=0,hjust=.5, 
                                   vjust=.5,face="plain"), 
        axis.text.y = element_text(colour="grey20",size=10,angle=0,hjust=1,
                                   vjust=0,face="plain"),  
        axis.title.x = element_text(colour="grey20",size=25,angle=0,hjust=.5,
                                    vjust=0,face="plain"),
        axis.title.y = element_text(colour="grey20",size=30,angle=90,hjust=.5,
                                    vjust=.5,face="plain"),
        legend.title = element_text(size=20),
        legend.text=element_text(size=20), legend.key.size = unit(1, 'in'))

It is interesting to note that missing grades are probably the most probable in general. There are some zip codes with almost no missing grades. The restuarants in those same zip codes seem to mostly have all A grades. Another interesting point is 1 zip code which has all Not Yet Graded grades. In general missing grades and grades of an A seem the most common. This is followed by grades of a B.

The next part of our analysis was to look at the average scores of each cuisine. With this information, we can help consumers see what kinds of establishments have the best and worst scores on average. This in term can help a consumer choose a type of cuisine when they are hungry.

average_scores <- ViolationsData %>% select(-CAMIS, -DBA, -BUILDING, -STREET, -ZIPCODE, 
                                            -PHONE, -INSPECTION.DATE, -ACTION, 
                                            -VIOLATION.CODE, -VIOLATION.DESCRIPTION, 
                                            -CRITICAL.FLAG, -GRADE, -GRADE.DATE, 
                                            -RECORD.DATE, -INSPECTION.TYPE)
average_cuisine <- average_scores %>%  group_by(CUISINE.DESCRIPTION) %>% na.omit %>%
  dplyr::summarize(average_score = mean(SCORE))
average_cuisine <- arrange(average_cuisine, -average_score)
ggplot(average_cuisine, aes(reorder(x = CUISINE.DESCRIPTION, --average_score), average_score,fill="Blue")) + geom_bar(stat='identity') + coord_flip() + theme(legend.position = 'none')+ggtitle("Average Scores Across Cuisines")+xlab("Cuisine Description")+ylab("Average Score")+geom_text(aes(label = sprintf("%.2f", average_score)),position=position_stack(vjust=0.5),vjust = 0.5, size = 3) + theme(plot.title = element_text(size = 25), 
        axis.title.x = element_text(colour="grey20",size=20,angle=0,hjust=.5,
                                    vjust=0,face="plain"),
        axis.title.y = element_text(colour="grey20",size=20,angle=90,hjust=.5,
                                    vjust=.5,face="plain"))

worst10 <- average_cuisine[1:10,]
ggplot(worst10, aes(reorder(x = CUISINE.DESCRIPTION, --average_score), average_score, fill = CUISINE.DESCRIPTION)) + geom_bar(stat='identity') + coord_flip() + theme(legend.position = 'none')+ggtitle("Average Score of Worst 10 cuisines")+xlab("Cuisine Description")+ylab("Average Score")+geom_text(aes(label = sprintf("%.2f", average_score)),position=position_stack(vjust=0.5))

n <- nrow(average_cuisine)
best10 <- average_cuisine[(n- 10):n,]
best10 <- best10[order(best10$average_score),]
ggplot(best10, aes(reorder(x = CUISINE.DESCRIPTION, -average_score), average_score, fill = CUISINE.DESCRIPTION)) + geom_bar(stat='identity') + coord_flip() + theme(legend.position = 'none')+ggtitle("Average Score of top 10 cuisines")+xlab("Cuisine Description")+ylab("Average Score")+geom_text(aes(label = sprintf("%.2f", average_score)),position = position_dodge(width = 1),#position=position_stack(vjust=0.5)
                                                                                                                     vjust = 0.5, size = 4)

It is interesting that the mysterious “not applicable”" cuisine is one of the best!

In this next plot, we wanted to see if the average score significantly differed by Borough.

average_borough <- average_scores %>%  group_by(BORO) %>% na.omit %>%
  dplyr::summarize(average_score = mean(SCORE))
ggplot(average_borough, aes(reorder(x = BORO, --average_score), average_score, fill = BORO)) + geom_bar(stat='identity') + theme(legend.position = 'none')+ggtitle("Average Score across boroughs")+xlab("Borough")+ylab("Average Score")+geom_text(aes(label = sprintf("%.2f", average_score)),position = position_dodge(width = 1),
                                                                                                                     vjust = -0.5, size = 3)

Although we see a slight difference, the boroughs have approximately the same average score. Staten Island may be the one exception. We also faceted by year to see if that made a difference, but the results were essentially the same as in the plot above.

So far we looked at grades and scores but not the actual violations. Whare are the top violations restaurants usually face?

violations<- ViolationsData %>% select(-CAMIS, -DBA, -BUILDING, -STREET, -ZIPCODE, 
                                            -PHONE, -INSPECTION.DATE, -ACTION, 
                                            -VIOLATION.CODE, 
                                            -CRITICAL.FLAG, -GRADE, -GRADE.DATE, 
                                            -RECORD.DATE, -INSPECTION.TYPE)
vs <- violations %>%  group_by(VIOLATION.DESCRIPTION) %>% na.omit %>%
  dplyr::summarize(count = n())
vs <- arrange(vs, -count)
topviolations <- vs[1:10,]
library(stringr)
topviolations$viol = str_wrap(topviolations$VIOLATION.DESCRIPTION, width = 15)
ggplot(topviolations, aes(reorder(viol, -count), count,fill=viol)) + geom_bar(stat='identity') + 
  theme(legend.position = 'none')+ggtitle("Top 10 violations \n")+xlab("Violation description")+ylab("count")+geom_text(aes(label = sprintf("%.0f", count)),
 position = position_dodge(width = 1), vjust = -0.5, size = 7) + theme(plot.title = element_text(size = 40, face = "bold"), 
        axis.text.x = element_text(colour="grey20",size=20,angle=0,hjust=.5, 
                                   vjust=.5,face="plain"), 
        axis.text.y = element_text(colour="grey20",size=30,angle=0,hjust=1,
                                   vjust=0,face="plain"),  
        axis.title.x = element_text(colour="grey20",size=30,angle=0,hjust=.5,
                                    vjust=0,face="plain"),
        axis.title.y = element_text(colour="grey20",size=30,angle=90,hjust=.5,
                                    vjust=.5,face="plain"))

We also hypothesized that the type of inspections would generally vary by Borough but even this was mostly consistent.

 library(viridis)
mos<- ViolationsData %>% select(-CAMIS, -DBA, -BUILDING, -STREET, -ZIPCODE, 
                                           -PHONE, -ACTION, 
                                           -VIOLATION.CODE, 
                                           -CRITICAL.FLAG, -GRADE, -GRADE.DATE, 
                                           -RECORD.DATE, -SCORE, -CUISINE.DESCRIPTION, -VIOLATION.DESCRIPTION)
mos$year <- factor(as.numeric( format(mos$INSPECTION.DATE , '%Y')))
mos <- mos %>% select(-INSPECTION.DATE)
average_mos <- mos %>% group_by(BORO, year, INSPECTION.TYPE) %>% tally %>%
 group_by(BORO, year)  %>% 
 mutate(percentage = n / sum(n))
#average_cuisine <- arrange(average_cuisine, -average_score)
library(vcd)
average_mos <- average_mos %>% select(-n)
ggplot(average_mos, aes(BORO, 
                       INSPECTION.TYPE, fill = percentage)) +
 geom_tile() +
 scale_fill_viridis() +
 #facet_wrap(~BORO)
 ggtitle("Average Violation Score by Zip Code\n ")+ xlab('Borough') +
 theme(plot.title = element_text(size = 30, face = "bold"), 
        axis.text.x = element_text(colour="grey20",size=20,angle=0,hjust=.5, 
                                   vjust=.5,face="plain"), 
        axis.text.y = element_text(colour="grey20",size=15,angle=0,hjust=1,
                                   vjust=0,face="plain"),  
        axis.title.x = element_text(colour="grey20",size=25,angle=0,hjust=.5,
                                    vjust=0,face="plain"),
        axis.title.y = element_text(colour="grey20",size=25,angle=90,hjust=.5,
                                    vjust=.5,face="plain"),
        legend.title = element_text(size=30),
        legend.text=element_text(size=20), legend.key.size = unit(1, 'in'))

One thing we noticed was that Staten island has the least Cycle Inspection/Initial Inspection. Maybe they were opening up the least new restuarants. They also had the most cycle inspection/re-inspection percentage. As we’ll see in the maps below, those reinspections may have helped restaurants in staten island improve their grades and scores.

One factor of our data set we haven’t discussed too much is the Critical flag, saying whether or not a violation is critical. Once again, we assumed that over the years and across different boroughs, there would be some pattern in critical violations. As can be inferred from the following plot, there is not.

CriticalityData<- ViolationsData %>% select(BORO,INSPECTION.DATE,CRITICAL.FLAG) %>% na.omit()
CriticalityData$year<-factor(as.numeric( format(CriticalityData$INSPECTION.DATE , '%Y')))
#CriticalityData <- CriticalityData %>%select(-INSPECTION.DATE)
CriticalityData <- CriticalityData %>%select(-INSPECTION.DATE) %>% gather(key, value, -BORO, -year) %>% group_by(BORO, year,key,value) %>% tally %>% spread(value, n, fill = 0)
names(CriticalityData)[5] <- "NotApplicable"
names(CriticalityData)[6] <- "NonCritical"
CriticalityDataPercentage<-CriticalityData %>% summarize(criticalPercent = Critical/(Critical+NotApplicable+NonCritical), NotApplicablePercent = NotApplicable/(Critical+NotApplicable+NonCritical), NonCriticalPercent = NonCritical/(Critical+NotApplicable+NonCritical))#
names(CriticalityDataPercentage)[4] <- "Critical"
names(CriticalityDataPercentage)[5] <- "NotApplicable"
names(CriticalityDataPercentage)[6] <- "NonCritical"
CriticalityDataPercentage<-CriticalityDataPercentage%>% gather(key1, value, -BORO, -year,-key)
ggplot(CriticalityDataPercentage, aes(BORO, value, fill = key1)) + 
  geom_bar(stat="identity", position = "dodge") + 
  scale_fill_brewer(palette = "Set1")+ xlab("BOROUGH")+ylab("CRITICALITY PERCENTAGES") +theme(axis.text.x = element_text(angle = 45, hjust = 1))+ ggtitle("Grouped barchart of grade frequency in each cuisine category \n when there was a violation reported")+facet_wrap(~year,nrow=2,ncol=3)+guides(fill=guide_legend(title="Criticality \n category"))

We attempted to further analyze the critical flag but we learned that the critical flag did not have too much of an impact on grade or scores. There was a lot of data with a grade of A but a Critical flag while others with a very high score by no critical flag.

VISUALISING DATA WITH MAPS

For the remainder of the analysis, we will be working with Tableau. We chose Tableau because we wanted to plot elegent maps. Tableau provides a simpler and more elegent way to do so than in R.

The code chunks will generate the data that we upload to Tableau. In the following analysis, we explore the proportion of A grades based on neighborhoods over the years.

#####GENERATE GRADE PROPOERTION DATASET BY YEAR FOR TABLEAU PLOTTING
zipdata<- ViolationsData %>% select(-CAMIS, -DBA, -BUILDING, -STREET, 
                                            -PHONE, -ACTION, 
                                            -VIOLATION.CODE, 
                                            -CRITICAL.FLAG, -SCORE, -GRADE.DATE, 
                                            -RECORD.DATE, -INSPECTION.TYPE, -VIOLATION.DESCRIPTION, -CUISINE.DESCRIPTION, -BORO) %>% na.omit()
zipdata$year<-factor(as.numeric( format(zipdata$INSPECTION.DATE , '%Y')))
zipdataGradePercentage <- zipdata %>%select(-INSPECTION.DATE) %>%gather(key, value, -ZIPCODE, -year) %>% group_by(ZIPCODE, year, key, value) %>%
  tally %>% spread(value, n, fill = 0) %>% summarize(aprop = A/(A+B+C), bprop = B/(A+B+C), cprop = C/(A+B+C) )
# ggplot(zipdataGradePercentage, aes(reorder(x = ZIPCODE, --aprop), aprop)) + geom_bar(stat='identity') + 
#   coord_flip() + facet_wrap(~year)
#   theme( axis.text = element_text(size = 3))

PROPORTIONS OF A GRADES ACROSS THE YEARS

Over the years, the proportion of A’s has increased, as represented by the darker red shading of the map. It is too early to study 2017 in depth due to the limited number of inspections so far. Note that 2017 data maps are only for the first quarter and are likely to change.

knitr::include_graphics('Aprop2013.png')

knitr::include_graphics('Aprop2014.png')

knitr::include_graphics('Aprop2015.png')

knitr::include_graphics('Aprop2016.png')

knitr::include_graphics('Aprop2017.png')

PROPORTIONS OF B GRADES ACROSS THE YEARS

Let’s look at how the proportion of B’s change. We can see that over the years, there seem to be less B’s which makes sense since over the years we have more grades of an A. Note that 2017 data maps are only for the first quarter and are likely to change.

knitr::include_graphics('Bprop1.png')

knitr::include_graphics('Bprop2.png')

knitr::include_graphics('Bprop3.png')

knitr::include_graphics('Bprop4.png')

knitr::include_graphics('Bprop5.png')

AVERAGE SCORES BY ZIPCODE OVER THE YEARS

Let’s look at the average scores by neighborhood. We have already done so by cuisine. Over the years, we have a lower Average Score. These NYC inspections must be prompting resturants to improve their facilities and follow regulations!

Note that 2017 data maps are only for the first quarter and are likely to change.

knitr::include_graphics('AvgScore2013.png')

knitr::include_graphics('AvgScore2014.png')

knitr::include_graphics('AvgScore2015.png')

knitr::include_graphics('AvgScore2016.png')

knitr::include_graphics('AvgScore2017.png')

AVERAGE RE-INSPECTIONS OVER THE YEARS

We decided to see which areas had the highest average number of reinspections. Maybe this can explain the better scores and grades over time. Interestingly enough, the areas with the most reinspections seem to be the ones in which the grades and scores improved. The system of reinspections must be working!

Note that 2017 data maps are only for the first quarter and are likely to change.

### DATA GENERATION
library(stringr)
reinspect <- ViolationsData %>% select(-CAMIS, -DBA, -BUILDING, -STREET, -CUISINE.DESCRIPTION, -BORO, -SCORE,
                                           -PHONE, INSPECTION.DATE, -ACTION, 
                                           -VIOLATION.CODE, 
                                           -CRITICAL.FLAG, -GRADE, -GRADE.DATE, 
                                           -RECORD.DATE)
reinspect$year<-factor(as.numeric( format(reinspect$INSPECTION.DATE , '%Y')))
#inspection_types <- INSPECTION.TYPE
reinspect <- reinspect %>%select(-INSPECTION.DATE)  %>% na.omit %>% filter(str_detect(INSPECTION.TYPE, "Re-inspection"))%>%group_by(VIOLATION.DESCRIPTION, year, ZIPCODE )  %>% tally# summarise(re = sum(str_count(reinspect$INSPECTION.TYPE, "Re-inspection")))
knitr::include_graphics('AvgReinspect2013.png')

knitr::include_graphics('AvgReinspect2014.png')

knitr::include_graphics('AvgReinspect2015.png')

knitr::include_graphics('AvgReinspect2016.png')

knitr::include_graphics('AvgReinspect2017.png')

STARBUCKS OR DUNKIN DONUTS???

As a last bit of analysis, we decided to help settle a huge dilemna : Where should you get your coffee, Starbucks or Dunkin Donuts?

##DATA GENERATION CODE
library(stringr)
ChainssDf<-ViolationsData%>% mutate(isStarbucks=str_detect(DBA,"STARBUCK")) %>% mutate(isDunkin=str_detect(DBA,"DUNKIN"))
ChainssDf$DBA[ChainssDf$isStarbucks==TRUE]<-"STARBUCKS"
ChainssDf$DBA[ChainssDf$isDunkin==TRUE]<-"DUNKIN"
ChainssDf<-ChainssDf%>%filter(DBA %in% c("STARBUCKS","DUNKIN"))
ChainssDf<-ChainssDf%>%select(DBA,BORO,INSPECTION.DATE,ZIPCODE,SCORE,VIOLATION.DESCRIPTION,GRADE)
ChainssDf$year<-factor(as.numeric( format(ChainssDf$INSPECTION.DATE , '%Y')))
ChainssDf<-ChainssDf%>%select(-INSPECTION.DATE)

As you can see, Dunkin Donuts has more critical violations than Starbucks in every Borough. Noticible differences are especially see in Brooklyn and Manhattan.

knitr::include_graphics('DS1.png')

Both establishments shared the same top violations. Let’s see what percentage of these violations each has.

knitr::include_graphics('DS2.png')

Once again, Starbucks is the winner!

What about the average score?

knitr::include_graphics('DS3.png')

Regardles of the year, the average score of Dunkin Donuts has been higher. The higher the score, the worse. Noticiable differences are seen in 2015 and the first three months of 2017.

Let’s see if the Borough you’re in should impact your choice.

knitr::include_graphics('DS4.png')

We guess when you’re in Brooklyn, it doesn’t matter but everywhere else, stick to Starbucks, especially you Staten Island folks!

Conclusion

Many limitations included the missing data itself. We learned that sometimes, what may seem like an intuitive hypothesis, is actually completely false. There were not as many patterns as we anticipated in terms of Borough. The only thing we did see is that over time, the score and grade has increased, showing us that the inspections seem to be working and that restuarants are improving their facilities to receieve a better score and grade. According to the website we received the data from, three fields are soon to be added as data logic becomes available to populate them accurately. Those fields are VIOLATION POINTS (the points assigned to a violation before or after adjudication, depending on whether adjudication has occurred), FINES TOTAL (the fine amount after adjudication), and DECISION DATE (adjudication date – or date a grade becomes final). With this information, we can do more analysis to see the fines of various violations and learn how each violation actually affects the score. With that, restaurants can focus on the main violations to improve their scores in the next inspection.

LS0tCnRpdGxlOiAiVG8gRWF0IE9yIE5vdCBUbyBFYXQ/IgphdXRob3I6ICJKb25hdGhhbiBHYWxzdXJrYXIgLSBqZmcyMTUwIHwgTGFrc2h5YSBHYXJnIC0gbGcyOTA2IgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCiMjIEludHJvZHVjdGlvbgoKSW4gb25lIG9mIHRoZSBncmVhdGVzdCBjaXRpZXMgaW4gdGhlIHdvcmxkLCBOWUMsIHdlIGhhdmUgYW4gYWJ1bmRhbnQgYW1vdW50IG9mIG9wdGlvbnMgdG8gY2hvb3NlIGZyb20gd2hlbiBpdCBjb21lcyB0byB3aGVyZSB3ZSBlYXQsIHdoZXRoZXIgd2UgYXJlIGxvb2tpbmcgZm9yIGZpbmUgZGluaW5nLCBmYXN0IGZvb2QsIG9yIHNvbWV0aGluZyBpbiBiZXR3ZWVuLiBUaGUgY3Vpc2luZSBjaG9pY2VzIHNlZW0ganVzdCBhcyBlbmRsZXNzLiBJcyB0aGVyZSBhIGdvb2Qgd2F5IHRvIGhlbHAgY2hvb3NlIGEgcmVzdGF1cmFudCB0byBlYXQgaW4gb3IgYSBnZW5lcmFsIGxvY2F0aW9uPyBBbHRob3VnaCBub3QgYW4gZWFzeSB0YXNrLCBvbmUgdGhpbmcgd2UgY2FuIGFsbCBhZ3JlZSBvbiBpcyB0aGF0IG5vYm9keSB3YW50cyB0byBlYXQgaW4gYSByZXN0YXVyYW50IHJpZGRlbiB3aXRoIGhlYWx0aCB2aW9sYXRpb25zLiBJbiBvdXIgcHJvamVjdCwgd2Ugd2lsbCBleHBsb3JlIGFuZCBhbmFseXplIE5ZQ+KAmXMgcmVzdGF1cmFudCBpbnNwZWN0aW9uIHJlc3VsdHMgZnJvbSAyMDEzLTIwMTcgb2YgdGhlIGZpdmUgYm9yb3VnaHMuCgpXZSBhcmUgaW50ZXJlc3RlZCBpbiBhbnN3ZXJpbmc6CgoxLiBXaGljaCBjdWlzaW5lcyBoYXZlIHRoZSBsZWFzdCBhbmQgbW9zdCB2aW9sYXRpb25zIGFsb25nIHdpdGggdGhlaXIgYXNzb2NpYXRlZCBzY29yZT8KCjIuIFdoYXQgcGFydHMgb2YgTllDIGhhdmUgdGhlIGxlYXN0IGFuZCBtb3N0IHZpb2xhdGlvbnM/CgozLiBXZSBoeXBvdGhlc2l6ZSB0aGF0IGxvY2F0aW9uIGlzIGhpZ2hseSBhc3NvY2lhdGVkIHdpdGggaW5zcGVjdGlvbiBncmFkZSBhbmQgc28gd2Ugd2lsbCBiZSBzZWFyY2hpbmcgZm9yIHBhdHRlcm5zIGJldHdlZW4gdGhlc2UgdmFyaWFibGVzLgoKNC4gV2UgYXJlIGFsc28gaW50ZXJlc3RlZCBpbiBzZWVpbmcgaG93IGluc3BlY3Rpb24gZ3JhZGUgYW5kIHNjb3JlIGNoYW5nZXMgb3ZlciB0aW1lICh5ZWFycykgYmFzZWQgdXBvbiBjdWlzaW5lIGFuZCBsb2NhdGlvbi4KCjUuIEEgZGVzY3JpcHRpb24gb2YgdmlvbGF0aW9ucyBpcyBhbHNvIHByb3ZpZGVkIGluIHRoZSBkYXRhIGFuZCB3ZSB3b3VsZCBsaWtlIHRvIGJldHRlciB1bmRlcnN0YW5kIHRoZSBjb21tb24gY2F1c2VzIG9mIHRob3NlIHZpb2xhdGlvbnMgYmFzZWQgdXBvbiB0aGUgZGVzY3JpcHRpb24uCgo2LiBJcyB0aGVyZSBhIHBhdHRlcm4vdHJlbmQgaW4gdmlvbGF0aW9ucy9pbnNwZWN0aW9uIHNjb3JlL2dyYWRlIGJhc2VkIHVwb24gcmVzdGF1cmFudCBjaGFpbnM/CgpXZSBmb3VuZCB0aGlzIGRhdGEgc2V0IGJ5IGV4cGxvcmluZyBOWUMgb3BlbiBkYXRhIHNldHMuIE91ciBsb3ZlIGZvciBmb29kIGFuZCBoZWFsdGggbWFkZSB0aGlzIGEgZ3JlYXQgb3B0aW9uLiBPbmUgb2Ygb3VyIHRlYW0gbWVtYmVycyB3aG8gcmVjZW50bHkgbW92ZWQgdG8gTllDIGlzIGhpZ2hseSBzZWxlY3RpdmUgYWJvdXQgdGhlIGZvb2Qgc2hlIGVhdHMgZHVlIHRvIGhlciByYXJlIGhlYWx0aCBjb25kaXRpb24gYW5kIGhlbmNlLCBoYXZpbmcgdGhlIGtpbmRzIG9mIHF1ZXN0aW9ucyBtZW50aW9uZWQgYWJvdmUgYW5zd2VyZWQgY2FuIHByb3ZlIHRvIGJlIGEgZ3JlYXQgYXNzZXQgdG8gYm90aCBoZXIgYW5kIG90aGVycyBzdWZmZXJpbmcgZnJvbSBoZWFsdGggY29uZGl0aW9ucyBvciB3aG8gbWF5IGp1c3QgYmUgcGlja3kgZWF0ZXJzISBUaGUgZGF0YSBjYW4gYmUgZm91bmQgYW5kIGRvd25sb2FkZWQgaGVyZTogaHR0cHM6Ly9kYXRhLmNpdHlvZm5ld3lvcmsudXMvSGVhbHRoL0RPSE1ILU5ldy1Zb3JrLUNpdHktUmVzdGF1cmFudC1JbnNwZWN0aW9uLVJlc3VsdHMveHg2Ny1rdDU5CgpUbyBkb3dubG9hZCwgY2xpY2sgdGhlIGV4cG9ydCBidXR0b24gb24gdGhlIHJpZ2h0IC0+IERvd25sb2FkIGFzIC0+IGNob29zZSB5b3VyIGZvcm1hdC4gRm9yIHRoaXMgYXNzaWduZW1lbnQgd2UgdXNlZCB0aGUgQ1NWIGZvcm1hdC4KCiMjIFRlYW0KCiMjIyMjIFRlYW0gTWVtYmVyczoKKyBKb25hdGhhbiBHYWxzdXJrYXIKKyBMYWtzaHlhIEdhcmcKCiMjIyMjIFRhc2sgRGlzdHJpYnV0aW9uCkdldCB0YWJsZSBmb3JtYXQgZm9yIHRoaXMKYGBge3J9CmxpYnJhcnkoa25pdHIpClRhc2tEaXN0cmlidXRpb24gPC0gcmVhZC5jc3YoZmlsZT0iVGFza0Rpc3RyaWJ1dGlvbi5jc3YiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyPVRSVUUsIHNlcD0iLCIsYXMuaXM9VFJVRSkKa2FibGUoVGFza0Rpc3RyaWJ1dGlvbiApCmBgYAoKIyMgQW5hbHlzaXMgb2YgRGF0YSBRdWFsaXR5ClRvIGJlZ2luIG91ciBhbmFseXNpcyBvZiBkYXRhIHF1YWxpdHksIGxldCdzIGxvYWQgb3VyIGRhdGEuCmBgYHtyfQpWaW9sYXRpb25zRGF0YSA8LSByZWFkLmNzdihmaWxlPSJpbnNwZWN0aW9uLmNzdiIsIGhlYWRlcj1UUlVFLCBzZXA9IiwiLCBhcy5pcz1UUlVFKQpgYGAKCiMjIyBEYXRhc2V0IERlc2NyaXB0aW9uCmBgYHtyfQpEYXRhc2V0RGVzY3JpcHRpb24gPC0gcmVhZC5jc3YoZmlsZT0iRGVzY3JpcHRpb25EYXRhc2V0LmNzdiIsIGhlYWRlcj1UUlVFLCBzZXA9IiwiLCBhcy5pcz1UUlVFKQprYWJsZShEYXRhc2V0RGVzY3JpcHRpb24gKQpgYGAKCkluIG9yZGVyIHRvIG1vcmUgZWFzaWx5IHdvcmsgd2l0aCBvdXIgZGF0YSBsZXQncyBlbnN1cmUgdGhhdCBldmVyeSBkYXRlIGlzIGluIGEgZGF0ZSBmb3JtYXQgcmF0aGVyIHRoYW4gYSBzdHJpbmcgYW5kIGV2ZXJ5IHN0cmluZyBpcyBhIGZhY3RvciB2YXJpYWJsZS4KYGBge3IgbWVzc2FnZT1GQUxTRX0KCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCiMjRGF0ZSBjb252ZXJzaW9uClZpb2xhdGlvbnNEYXRhIDwtIFZpb2xhdGlvbnNEYXRhICU+JQogIG11dGF0ZShJTlNQRUNUSU9OLkRBVEU9IGFzLkRhdGUoSU5TUEVDVElPTi5EQVRFLCBmb3JtYXQ9ICIlbS8lZC8lWSIpKSU+JQogIG11dGF0ZShHUkFERS5EQVRFPSBhcy5EYXRlKEdSQURFLkRBVEUsIGZvcm1hdD0gIiVtLyVkLyVZIikpJT4lCiAgbXV0YXRlKFJFQ09SRC5EQVRFPSBhcy5EYXRlKFJFQ09SRC5EQVRFLCBmb3JtYXQ9ICIlbS8lZC8lWSIpKQojI0ZhY3RvciBjb252ZXJzaW9uClZpb2xhdGlvbnNEYXRhIDwtIFZpb2xhdGlvbnNEYXRhICU+JQogIG11dGF0ZShDVUlTSU5FLkRFU0NSSVBUSU9OPSBhcy5mYWN0b3IoQ1VJU0lORS5ERVNDUklQVElPTikpJT4lCiAgbXV0YXRlKEJPUk89IGFzLmZhY3RvcihCT1JPKSklPiUKICBtdXRhdGUoVklPTEFUSU9OLkNPREU9IGFzLmZhY3RvcihWSU9MQVRJT04uQ09ERSkpJT4lCiAgbXV0YXRlKENSSVRJQ0FMLkZMQUc9IGFzLmZhY3RvcihDUklUSUNBTC5GTEFHKSklPiUKICBtdXRhdGUoR1JBREU9IGFzLmZhY3RvcihHUkFERSkpJT4lCiAgbXV0YXRlKElOU1BFQ1RJT04uVFlQRT0gYXMuZmFjdG9yKElOU1BFQ1RJT04uVFlQRSkpCgpzdW1tYXJpemUgPC0gZHBseXI6OnN1bW1hcml6ZQptdXRhdGUgPC0gZHBseXI6Om11dGF0ZQpgYGAKCgpNYW55IG9mIHRoZSBxdWVzdGlvbnMgd2UgYXJlIGludGVyZXN0ZWQgaW4gYW5zd2VyaW5nIGludm9sdmUgdHJlbmRzIGFjcm9zcyByZXN0dWFyYW50IGxvY2F0aW9ucy4gV2UgZmlyc3QgY2hlY2tlZCB0byBzZWUgdGhlIG51bWJlciBvZiByZXN0dWFyYW50IGluc3BlY3Rpb25zIGJ5IGJvcm91Z2guIEZyb20gdGhlIHBsb3QgYmVsb3csIHdlIG5vdGljZWQgdGhhdCB0aGVyZSB3ZXJlIGEgbnVtYmVyIG9mIGluc3BlY3Rpb25zIGluIHdoaWNoIHRoZSBCb3JvdWdoIGluZm9ybWF0aW9uIHdhcyBtaXNzaW5nIGFuZCB3b24ndCBoZWxwIG91ciBhbmFseXNpcy4KYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKYm9yb3VnaFBsb3QgPC0gZ2dwbG90KFZpb2xhdGlvbnNEYXRhLCBhZXMoQk9STywgZmlsbD1CT1JPKSkKYm9yb3VnaFBsb3QgKyBnZW9tX2JhcigpKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSArCiAgZ2d0aXRsZSgnSW5zcGVjdGlvbiBDb3VudCBieSBCb3JvdWdoJykgKyAKICBsYWJzKHggPSAiQm9yb3VnaCIsIHkgPSJOdW1iZXIgb2YgSW5zcGVjdGlvbnMiKQpgYGAKCkFub3RoZXIgbWFpbiBmZWF0dXJlIG9mIG91ciBkYXRhIHNldCBpcyB0aGUgaW5zcGVjdGlvbiB5ZWFyIHNpbmNlIHdlIHdpc2ggdG8gZXhwbG9yZSBwYXR0ZXJzIGluIGluc3BlY3Rpb24gZ3JhZGVzL3Njb3JlcyBvdmVyIHRoZSB5ZWFycy4gRnJvbSB0aGUgcGxvdCBiZWxvdywgd2Ugbm90aWNlZCB0aGF0IHRoZXJlIGlzIGFsbW9zdCBubyBpbnNwZWN0aW9uIGRhdGEgYmVmb3JlIDIwMTMgYW5kIHN1cnByaXNpbmdseSBtb3JlIGluc3BlY3Rpb24gZGF0YSBpbiAxOTAwIHRoYW4gMjAxMiBhbmQgMjAxMS4gV2UgZGVjaWRlZCB0byBvbmx5IHdvcmsgd2l0aCBkYXRhIGZyb20gMjAxMyBhbmQgdXAuIEl0IGlzIGltcG9ydGFudCB0byBub3RlIHRoYXQgdGhlIG51bWJlciBvZiBpbnNwZWN0aW9ucyBmb3IgMjAxNyBpcyBsb3cgc2luY2UgdGhlIDIwMTcgZGF0YSBpcyBvbmx5IGF2YWlsYWJsZSBmcm9tIEphbnVhcnkgLSBNYXJjaC4KYGBge3J9CnZpb2xhdGlvblllYXJzUGxvdCA8LSBnZ3Bsb3QoVmlvbGF0aW9uc0RhdGEsIGFlcyhmYWN0b3IoYXMubnVtZXJpYyhmb3JtYXQoSU5TUEVDVElPTi5EQVRFLCAnJVknKSkpLCBmaWxsPSJSZWQiKSkKdmlvbGF0aW9uWWVhcnNQbG90ICsgZ2VvbV9iYXIoKStjb29yZF9mbGlwKCkrIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICt4bGFiKCJJbnNwZWN0aW9uIFllYXIiKSt5bGFiKCJJbnNwZWN0aW9uIENvdW50cyIpICsgZ2d0aXRsZSgiSW5zcGVjdGlvbiBDb3VudCBieSBZZWFyIikKYGBgCgpBbm90aGVyIGNydWNpYWwgZmVhdHVyZSBvZiBvdXIgZGF0YSBzZXQgaXMgZ3JhZGUgYSByZXN0YXVyYW50IHJlY2VpdmVkIGFmdGVyIGluc3BlY3Rpb24uIFdlIGRlY2lkZWQgdG8gcGxvdCBhIHN0YWNrZWQgYmFyIGNoYXJ0IGZvciB0byBzZWUgdGhlIGNvdW50IG9mIGVhY2ggdHlwZSBvZiBncmFkZSBmb3IgZXZlcnkgY3Vpc2luZS4gV2UgdXNlZCBhIHN0YWNrZWQgYmFyIGNoYXJ0IGJlY2F1c2Ugd2Ugd2FudGVkIHRvIHF1aWNrbHkgYXNzZXNzIHRoZSBtYWduaXR1ZGUgbWlzc2luZyB3aXRob3V0IHRha2luZyB1cCBleHRyYSByb29tLiBGcm9tIHRoZSBwbG90IGJlbG93LCBpdCB3YXMgc2hvY2tpbmcgdG8gc2VlIHRoYXQgd2UgZ2VuZXJhbGx5IGhhZCBtb3JlIG1pc3NpbmcgZ3JhZGVzIHRoYW4gZ3JhZGVzLiBUaGlzIHdhcyB0cnVlIHJlZ2FyZGxzcyBvZiBjdWlzaW5lLiBJdCB3YXMgYWxzbyBpbnRlcmVzdGluZyB0aGF0IHRoZSBncmFkZXMgd2VyZSBwdXJlbHkgbWlzc2luZyBhbmQgbm90IGNhdGVnb3JpemVkIGFzICJOb3QgWWV0IEdyYWRlZCIuCmBgYHtyLGZpZy5oZWlnaHQ9MTd9CmdncGxvdChWaW9sYXRpb25zRGF0YSwgYWVzKENVSVNJTkUuREVTQ1JJUFRJT04sIGZpbGwgPSBHUkFERSkpICsgZ2VvbV9iYXIoKSArIAogIGNvb3JkX2ZsaXAoKSArIGdndGl0bGUoIlN0YWNrZWQgQmFyIENoYXJ0IG9mIEdyYWRlIERpc3RyaWJ1dGlvbnMgQWNyb3NzIEN1aXNpbmVzIikgKwogIHhsYWIoJ0N1aXNpbmUnKSArIHlsYWIoJ0NvdW50JykgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcwLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3VyPSJncmV5MjAiLHNpemU9NjAsYW5nbGU9MCxoanVzdD0uNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmp1c3Q9LjUsZmFjZT0icGxhaW4iKSwgCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoY29sb3VyPSJncmV5MjAiLHNpemU9MzAsYW5nbGU9MCxoanVzdD0xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZqdXN0PTAsZmFjZT0icGxhaW4iKSwgIAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChjb2xvdXI9ImdyZXkyMCIsc2l6ZT02MCxhbmdsZT0wLGhqdXN0PS41LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2anVzdD0wLGZhY2U9InBsYWluIiksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KGNvbG91cj0iZ3JleTIwIixzaXplPTUwLGFuZ2xlPTkwLGhqdXN0PS41LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2anVzdD0uNSxmYWNlPSJwbGFpbiIpLAogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTQwKSwKICAgICAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT00MCksIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMSwgJ2luJykpCmBgYAoKVG8gc3RheSBvbiB0aGUgdG9waWMgb2YgZ3JhZGVzLCBhZnRlciByZXNlYXJjaGluZyB0aGUgbGV0dGVyIGdyYWRpbmcgcHJvZ3JhbSwgd2UgZm91bmQgdGhlIGZvbGxvd2luZyBpbmZvcm1hdGlvbjogCgorIEJsYW5rIEdyYWRlIChSZWQpOiBUaGUgZm9sbG93aW5nIGFyZSBzY29yZWQgYnV0IG5vdCBncmFkZWQuIEZvciBleC4gSW5pdGlhbCBpbnNwZWN0aW9ucyB0aGF0IHJlc3VsdCBpbiBhIHNjb3JlIG9mIDE0IHBvaW50cyBvciBoaWdoZXIsIG1vbml0b3JpbmcgaW5zcGVjdGlvbnMgYXQgYSByZXN0YXVyYW50IHRoYXQgaGFzIHBlcmZvcm1lZCB2ZXJ5IHBvb3JseSBvbiBpdHMgcmUtaW5zcGVjdGlvbi4gVGhlIEhlYWx0aCBEZXBhcnRtZW50IG1heSBjb250aW51ZSB0byBpbnNwZWN0IHRoZSByZXN0YXVyYW50IHJvdWdobHkgb25jZSBhIG1vbnRoIHVudGlsIGl0IHNjb3JlcyBiZWxvdyAyOCBvciB0aGUgRGVwYXJ0bWVudCBjbG9zZXMgaXQgZm9yIHNlcmlvdXMgYW5kIHBlcnNpc3RlbnQgdmlvbGF0aW9ucywgaW5zcGVjdGlvbnMgYXQgbmV3IHJlc3RhdXJhbnRzIG5vdCB5ZXQgb3BlbiB0byB0aGUgcHVibGljLCBhbiBpbnNwZWN0aW9uIGF0IGEgcmVzdGF1cmFudCBzZWVraW5nIHRvIHJlb3BlbiBhZnRlciB0aGUgRGVwYXJ0bWVudCBjbG9zZWQgaXQsIHNvbWUgaW5zcGVjdGlvbnMgaW4gcmVzcG9uc2UgdG8gY29tcGxhaW50cy4KCisgQSBzY29yZSBvZiBsZXNzIHRoYW4gMTQgcG9pbnRzIG9uIGVpdGhlciBpbml0aWFsIG9yIHJlLWluc3BlY3Rpb24gcmVzdWx0cyBpbiBhbiDigJxB4oCdIGdyYWRlCgorIE9uIHJlLWluc3BlY3Rpb24sIGEgc2NvcmUgb2YgMTQtMjcgcG9pbnRzIG1lYW5zIGEgcmVzdGF1cmFudCByZWNlaXZlcyBib3RoIGEg4oCcQuKAnSBncmFkZSBhbmQgYSDigJxHcmFkZSBQZW5kaW5n4oCdIGNhcmQuCgorIE9uIHJlLWluc3BlY3Rpb24sIGEgc2NvcmUgb2YgMjggb3IgbW9yZSBwb2ludHMgbWVhbnMgYSByZXN0YXVyYW50IHJlY2VpdmVzIGJvdGggYSDigJxD4oCdIGdyYWRlIGFuZCBhIOKAnEdyYWRlIFBlbmRpbmfigJ0gY2FyZC4KCisgQm90aCBaIGFuZCBQIHJlcHJlc2VudCBncmFkZSBwZW5kaW5nLCBob3dldmVyIFAgcmVwcmVzZW50cyBhIEdyYWRlIFBlbmRpbmcgaXNzdWVkIG9uIHJlLW9wZW5pbmcgZm9sbG93aW5nIGFuIGluaXRpYWwgaW5zcGVjdGlvbiB0aGF0IHJlc3VsdGVkIGluIGEgY2xvc3VyZS4KCldlIGFsc28gZGlzY292ZXJlZCB0aGF0IG5vdCBldmVyeSBpbnNwZWN0aW9uIGlzICJncmFkYWJsZSIuIEdyYWRhYmxlIGluc3BlY3Rpb25zIGhhdmUgdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOgoKKyBJTlNQRUNUSU9OIFRZUEUgaW4gKEN5Y2xlIEluc3BlY3Rpb24vSW5pdGlhbCBJbnNwZWN0aW9uLCBDeWNsZSBJbnNwZWN0aW9uL1JlLUluc3BlY3Rpb24sIFByZS1QZXJtaXQgKE9wZXJhdGlvbmFsKS9Jbml0aWFsIEluc3BlY3Rpb24sIFByZS1QZXJtaXQgKE9wZXJhdGlvbmFsKS9SZS1JbnNwZWN0aW9uKQoKKyBBQ1RJT04gaW4gKFZpb2xhdGlvbnMgd2VyZSBjaXRlZCBpbiB0aGUgZm9sbG93aW5nIGFyZWEocyksIE5vIHZpb2xhdGlvbnMgd2VyZSByZWNvcmRlZCBhdCB0aGUgdGltZSBvZiB0aGlzIGluc3BlY3Rpb24sIEVzdGFibGlzaG1lbnQgQ2xvc2VkIGJ5IERPSE1IKQoKKyBJTlNQRUNUSU9OIERBVEUgPiBKdWx5IDI2LCAyMDEwCgpUaGlzIGNhbiBwcm9iYWJseSBleHBsYWluIGEgZmFpciBhbW91bnQgb2YgdGhlIG1pc3NpbmcgZ3JhZGUgZGF0YSBvYnNlcnZlZCBpbiBvdXIgcGxvdC4KCkFjY29yZGluZyB0byB0aGUgQUJPVVQgdGhlIGRhdGEgc2V0IHBhZ2U6IFRoZSBTQ09SRSBhbmQgR1JBREUgZmllbGRzIG1heSBiZSBpbmNvbnNpc3RlbnQgd2l0aCBlYWNoIG90aGVyIGJlY2F1c2Ugb2YgbGltaXRhdGlvbnMgb3IgZXJyb3JzIGluIHRoZSBkYXRhIHN5c3RlbXMuIFRoYXQgaXMgdG8gc2F5LCBzY29yZXMgb2YgMC0xMywgMTQtMjcgYW5kIDI4KyBhcmUgbm90IGFsd2F5cyBhY2NvbXBhbmllZCBieSBBLCBCIGFuZCBDIGdyYWRlcywgcmVzcGVjdGl2ZWx5LCB3aGVuIHRoZXkgc2hvdWxkIGJlLiBUaGVyZSBtYXkgYWxzbyBiZSBjYXNlcyB3aGVyZSBhIGdyYWRlIGNhcmQgd2FzIGdpdmVuIG91dCBidXQgYSByZWNvcmQgb2YgdGhhdCBncmFkZSBpc3N1YW5jZSBpcyBtaXNzaW5nIGZyb20gdGhlIGRhdGEgc3lzdGVtLCBhbmQgdGhlcmVmb3JlIG1pc3NpbmcgZnJvbSB0aGlzIGRhdGFzZXQsIGV2ZW4gdGhvdWdoIHRoZSBTQ09SRSBmaWVsZCBpcyBwb3B1bGF0ZWQuICBOb3RlIHRoYXQgd2hlbiBpbml0aWFsIGluc3BlY3Rpb25zIGFyZSBhZGp1ZGljYXRlZCBkb3duIHRvIHRoZSBBIHJhbmdlLCB0aGUgYWJzZW5jZSBvZiBhbiBhY2NvbXBhbnlpbmcgZ3JhZGUgYXNzb2NpYXRlZCB3aXRoIHRoYXQgaW5zcGVjdGlvbiBpcyBjb3JyZWN0LCBiZWNhdXNlIHRoZSBncmFkZSB3b3VsZCBub3QgYmUgYXNzaWduZWQgdW50aWwgdGhlIHJlLWluc3BlY3Rpb24gaXMgcGVyZm9ybWVkLiAKClRvIGdhaW4gc29tZSBmaW5hbCBpbnNpZ2h0IG9uIHRoZSBkYXRhIHF1YWxpdHksIHdlIGRlY2lkZWQgdG8gcGxvdCB0aGUgcmVsYXRpb25zaHAgYmV0d2VlbiB0aGUgbnVtYmVyIG9mIG1pc3Npbmcgc2NvcmVzIGJ5IGdyYWRlIGFuZCBieSB3aGV0aGVyIG9yIG5vdCBhIHZpb2xhdGlvbiB3YXMgcmVwb3J0ZWQuIFdlIHRyYW5zZm9ybWVkIHRoZSBhY3Rpb25zIHRha2VuIGludG8gdGhyZWUgY2F0ZWdvcmllczoKMS4gTm8gdmlvbGF0aW9ucyB3ZXJlIHJlY29yZGVkIGF0IHRoZSB0aW1lIG9mIHRoaXMgaW5zcGVjdGlvbiB0byBObyBWaW9sYXRpb24KMi4gQW55IGFjdGlvbiByZXBvcnRlZCB0byBWaW9sYXRpb24gcmVwb3J0ZWQKMy4gTWlzc2luZyBhY3Rpb25zIHRvIE5BCldlIHRoZW4gY291bnRlZCBpZiB0aGUgc2NvcmUgd2FzIHByb3ZpZGVkIG9yIG5vdC4KCmBgYHtyLGZpZy5oZWlnaHQ9NCwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KHBseXIpCnNjb3JlX2dyYWRlIDwtIFZpb2xhdGlvbnNEYXRhWyAtYygxOjksIDExOjEzLCAxNjoxOCkgXQpzY29yZV9ncmFkZVtzY29yZV9ncmFkZSA9PSAnJ10gPC0gTkEKc2NvcmVfZ3JhZGVfY29tYm9zIDwtIHNjb3JlX2dyYWRlICAlPiUgbXV0YXRlKG1pc3Npbmdfc2NvcmUgPSBpZmVsc2UoaXMubmEoU0NPUkUpLCAieWVzIiwgIm5vIikpCnNjb3JlX2dyYWRlX21pc3NpbmcgPC0gY291bnQoc2NvcmVfZ3JhZGVfY29tYm9zLCBjKCdHUkFERScsICdtaXNzaW5nX3Njb3JlJywgJ0FDVElPTicpKQpzY29yZV9ncmFkZV9taXNzaW5nIDwtIHNjb3JlX2dyYWRlX21pc3NpbmcgJT4lIAogIG11dGF0ZSh2aW9sYXRpb24gPSBpZmVsc2UoQUNUSU9OID09ICdObyB2aW9sYXRpb25zIHdlcmUgcmVjb3JkZWQgYXQgdGhlIHRpbWUgb2YgdGhpcyBpbnNwZWN0aW9uLicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTm8gdmlvbGF0aW9uIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoaXMubmEoc2NvcmVfZ3JhZGVfbWlzc2luZyRBQ1RJT04pLCBOQSwgIlZpb2xhdGlvbiBSZXBvcnRlZCIpKSkgJT4lCiAgc2VsZWN0KC1jKEFDVElPTikpCmdncGxvdChzY29yZV9ncmFkZV9taXNzaW5nLCBhZXMoeCA9IEdSQURFLCB5ID0gbG9nKGZyZXEpLCBmaWxsID0gbWlzc2luZ19zY29yZSkpICsgCiAgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsIHBvc2l0aW9uID0gJ2RvZGdlJykgKyBmYWNldF93cmFwKH52aW9sYXRpb24pICsgCiAgZ2d0aXRsZSgiR3JhZGUgYW5kIG1pc3Npbmcgc2NvcmUgY29tYmluYXRpb25zIGJ5IHZpb2xhdGlvbiByZXBvcnQiKSArCiAgbGFicyh4ID0gIkdyYWRlIiwgeSA9ICJMb2coRnJlcXVlbmN5KSIpCmBgYAoKT3RoZXIgaW50ZXJlc3RpbmcgaW5zaWdodHMgYnkgZXhwbG9yaW5nIGRhdGEgbWFudWFsbHkgYXJlOgoKMS4gR3JhZGVzIG9mIE5BIHJlcG9ydGVkIGhpZ2ggZnJlcXVlbmN5IG9mIHNjb3JlIGluIGJvdGggTm8gdmlvbGF0aW9uIGFuZCB2aW9sYXRpb24gcmVwb3J0ZWQgY2F0ZWdvcnkuCgoyLiBBdCBmaXJzdCwgaXQgYXBwZWFycyB0aGF0IGhpZ2ggc2NvcmVzIGFyZSByZWxhdGVkIHRvIGxvdyBncmFkZXMgb3IgbmVlZHMgZ3JhZGluZyBidXQgdGhlbiB3ZSBmaW5kIHJlc3RhdXJhbnRzIHdpdGggYSBncmFkZSBvZiBBIHRoYXQgaGFzIHRoZSBzYW1lIHNjb3JlIGFzIGEgcmVzdGF1cmFudCB3aXRoIGEgZ3JhZGUgb2YgQy4KCjMuIEFub3RoZXIgaW5zaWdodCBieSBqdXN0IGxvb2tpbmcgYXQgdGhlIGRhdGEgaXMgd2Ugc3VycHJpc2luZ2x5IHNhdyB0aGF0IHJlc3RhdXJhbnRzIHdpdGggYSBjcml0aWNhbCBmbGFnIHN0aWxsIHJlY2VpdmUgZ3JhZGVzIG9mIEEuCgojIyBFeGVjdXRpdmUgU3VtbWFyeQpOWUMgcHV0cyBhIGxvdCBvZiB0aW1lIGFuZCBtb25leSBpbnRvIGluc3BlY3RpbmcgcmVzdHVhcmFudHMuIFdoeSB3b3VsZCB0aGV5IGdvIHRocm91Z2ggYWxsIHRoaXMgdHJvdWJsZT8gVG8gZW5zdXJlIHF1YWxpdHkgbWVhbHMgYW5kIHNhdGlzZmFjdGlvbiBvZiBOWUMgcmVzaWRlbnRzIG9mIGNvdXJzZSEgSXQgaXMgaW1wb3J0YW50IGZvciByZXN0YXVyYW50cyBhbmQgZXN0YWJsaXNobWVudCB0aGF0IG1lZXQgYXQgbGVhc3QgdGhlIG1pbmltdW0gcmVxdWlyZW1lbnRzIG9mIGhlYWx0aCBhbmQgc2FmZXR5IHJlZ3VsYXRpb25zIGluIG9yZGVyIHRvIHByb21vdGUgbGVzcyBmb29kIHNpY2sgcmVzaWRlbnRzIGFuZCBhIGNsZWFuZXIgTllDLiAKCkhvdyBjYW4gd2UgdGVsbCBpZiB0aGVzZSBpbnNwZWN0aW9ucyBhcmUgYWN0dWFsbHkgd29ya2luZyB0byBwcm9tb3RlIHJlc3R1YXJhbnRzIHRvIG1lZXQgcmVndWxhdGlvbnM/IExldCdzIHRha2UgYSBsb29rIGF0IHRoZSBwcm9wb3J0aW9uIG9mIHNjb3JlcyB0aGF0IHdlcmUgZ3JhZGVkIGFuIEEgaW4gMjAxMyBhbmQgdGhlIHByb3BvcnRpb24gb2Ygc2NvcmVzIHRoYXQgd2VyZSBncmFkZWQgYW4gQSBpbiAyMDE2LiBMZXQncyB2aWV3IHRoaXMgaW5mb3JtYXRpb24gYnkgbG9jYXRpb24gc28gdGhhdCB5b3Uga25vdyB3aGF0IG5laWdoYm9yaG9vZCB0byBjaG9vc2Ugd2hlbiB5b3UncmUgY3JhdmluZyBhIHJlc3RhdXJhbnQuCgpgYGB7ciBlY2hvID0gRkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdBcHJvcDIwMTMucG5nJykKYGBgCgpgYGB7ciBlY2hvID0gRkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdBcHJvcDIwMTYucG5nJykKYGBgCgpUaGUgZGFya2VyIHRoZSBzaGFkZSBvZiByZWQsIHRoZSBoaWdoZXIgcHJvcG9ydGlvbiBvZiBBIGdyYWRlcy4gSXQgaXMgY2xlYXIgdGhhdCBvdmVyIHRoZSBsYXN0IDMgeWVhcnMsIHRoZSBwcm9wb3J0aW9uIG9mIHJlc3R1YXJhbnRzIHdpdGggYSBncmFkZSBvZiBhbiBBIGhhcyBpbnNjcmVhc2VkLiBOWUMgaW5zcGVjdGlvbnMgbXVzdCBiZSB3b3JraW5nIHRvIGltcHJvdmUgdGhlIHF1YWxpdHkgb2YgdGhlIHJlc3R1YXJhbnRzIHdlIGVhdCBpbiEgSW4gMjAxMywgaXQgc2VlbXMgdGhhdCB0aGUgdGhlcmUgd2VyZSBvbmx5IGEgZmV3IG5laWdoYm9yaG9vZHMgd2l0aCBhbiBleHRyZW1lbHkgaGlnaCBwcm9wb3J0aW9uIG9mIEEgZ3JhZGVzLiBPbmx5IDMgYXJlYXMgaW4gU3RhdGVuIElzbGFuZCwgMSBpbiBMb25nIElzbGFuZCwgYSBmZXcgaW4gQnJvb2tseW4sIE1hbmhhdHRhbiwgYW5kIHRoZSBCcm9ueC4gSW4gMjAxNiBvbiB0aGUgb3RoZXIgaGFuZCwgdGhlIHByb3BvcnRpb24gb2YgQSBncmFkZXMgaXMgdmVyeSBoaWdoIGFsbW9zdCByZWdhcmRsZXNzIG9mIG5laWdoYm9yaG9vZC4gS2VlcCBpdCB1cCBOWUMgcmVzdHVhcmFudCBpbnNwZWN0aW9ucyEKClRvIHNlZSB0aGlzIGZyb20gYW5vdGhlciBhbmdsZSwgbGV0J3MgbG9vayBhdCB0aGUgYXZlcmFnZSBzY29yZXMgcGVyIG5laWdoYm9yaG9vZCBpbiAyMDEzIGFuZCAyMDE2LiBBdmVyYWdlIHNjb3JlcyB3ZXJlIGJpbm5lZCBpbiB0aGUgbGVnZW5kIHN1Y2ggdGhhdDoKCisgMSBtZWFucyB0aGF0IHRoZSBhdmVyYWdlIHNjb3JlIHdhcyBsZXNzIHRoYW4gMTAsCgorIDIgaWYgdGhlIGF2ZXJhZ2Ugc2NvcmUgd2FzIGJldHdlZW4gMTAgYW5kIDIwLCAKCisgMyBpZiB0aGUgYXZlcmFnZSBzY29yZSB3YXMgYmV0d2VlbiAyMCBhbmQgMzAsCgorIDQgaWYgdGhlIGF2ZXJhZ2Ugc2NvcmUgd2FzIGJldHdlZW4gMzAgYW5kIDQwLAoKKyA1IGlmIGl0IHdhcyBiZXR3ZWVuIGEgNDAgYW5kIDUwLgoKYGBge3IgZWNobyA9IEZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygnQXZnU2NvcmUyMDEzLnBuZycpCmBgYAoKYGBge3IgZWNobyA9IEZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygnQXZnU2NvcmUyMDE2LnBuZycpCmBgYAoKSnVzdCBsb29rIGhvdyBtdWNoIGxpZ2h0ZXIgdGhlIGNvbG9yIG9mIGVhY2ggbmVpZ2hib3Job29kIGdvdCEgTGlnaHRlciBjb2xvcnMgbWVhbiBhIGxvd2VyIGF2ZXJhZ2Ugc2NvcmUsIHdoaWNoIGNvcnJlbGF0ZXMgdG8gbGVzcyB2aW9sYXRpb25zIGFuZCBoZWFsdGhpZXIgZXN0YWJsaXNobWVudHMhCgpXZWxsIG5vdyB3ZSBrbm93IHdoYXQgbmVpZ2hib3Job29kcyB0byBiZSBpbiBmb3IgZXN0YWJsaXNobWVudHMgd2l0aCBsb3cgc2NvcmVzIGFuZCBBIGdyYWRlcywgYnV0IHdoYXQgYXJlIHRoZSBhdmVyYWdlIHNjb3JlcyBiYXNlZCB1cG9uIGN1aXNpbmU/CgpgYGB7ciBlY2hvID0gRkFMU0V9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmF2ZXJhZ2Vfc2NvcmVzIDwtIFZpb2xhdGlvbnNEYXRhICU+JSBzZWxlY3QoLUNBTUlTLCAtREJBLCAtQlVJTERJTkcsIC1TVFJFRVQsIC1aSVBDT0RFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtUEhPTkUsIC1JTlNQRUNUSU9OLkRBVEUsIC1BQ1RJT04sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC1WSU9MQVRJT04uQ09ERSwgLVZJT0xBVElPTi5ERVNDUklQVElPTiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLUNSSVRJQ0FMLkZMQUcsIC1HUkFERSwgLUdSQURFLkRBVEUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC1SRUNPUkQuREFURSwgLUlOU1BFQ1RJT04uVFlQRSkKCmF2ZXJhZ2VfY3Vpc2luZSA8LSBhdmVyYWdlX3Njb3JlcyAlPiUgZ3JvdXBfYnkoQ1VJU0lORS5ERVNDUklQVElPTikgJT4lIG5hLm9taXQgJT4lCiAgZHBseXI6OnN1bW1hcml6ZShhdmVyYWdlX3Njb3JlID0gbWVhbihTQ09SRSkpCmF2ZXJhZ2VfY3Vpc2luZSA8LSBhcnJhbmdlKGF2ZXJhZ2VfY3Vpc2luZSwgLWF2ZXJhZ2Vfc2NvcmUpCmBgYAoKYGBge3IgZWNobz1GQUxTRX0KbiA8LSBucm93KGF2ZXJhZ2VfY3Vpc2luZSkKYmVzdDEwIDwtIGF2ZXJhZ2VfY3Vpc2luZVsobi0gMTApOm4sXQpiZXN0MTAgPC0gYmVzdDEwW29yZGVyKGJlc3QxMCRhdmVyYWdlX3Njb3JlKSxdCgpnZ3Bsb3QoYmVzdDEwLCBhZXMocmVvcmRlcih4ID0gQ1VJU0lORS5ERVNDUklQVElPTiwgLWF2ZXJhZ2Vfc2NvcmUpLCBhdmVyYWdlX3Njb3JlLCBmaWxsID0gQ1VJU0lORS5ERVNDUklQVElPTikpICsgZ2VvbV9iYXIoc3RhdD0naWRlbnRpdHknKSArIGNvb3JkX2ZsaXAoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykrZ2d0aXRsZSgiQXZlcmFnZSBTY29yZSBvZiB0b3AgMTAgY3Vpc2luZXMiKSt4bGFiKCJDdWlzaW5lIERlc2NyaXB0aW9uIikreWxhYigiQXZlcmFnZSBTY29yZSIpK2dlb21fdGV4dChhZXMobGFiZWwgPSBzcHJpbnRmKCIlLjJmIiwgYXZlcmFnZV9zY29yZSkpLHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAxKSwjcG9zaXRpb249cG9zaXRpb25fc3RhY2sodmp1c3Q9MC41KQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2anVzdCA9IDAuNSwgc2l6ZSA9IDQpCmBgYAoKYGBge3IgZWNobyA9IEZBTFNFfQp3b3JzdDEwIDwtIGF2ZXJhZ2VfY3Vpc2luZVsxOjEwLF0KCmdncGxvdCh3b3JzdDEwLCBhZXMocmVvcmRlcih4ID0gQ1VJU0lORS5ERVNDUklQVElPTiwgLS1hdmVyYWdlX3Njb3JlKSwgYXZlcmFnZV9zY29yZSwgZmlsbCA9IENVSVNJTkUuREVTQ1JJUFRJT04pKSArIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykgKyBjb29yZF9mbGlwKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpK2dndGl0bGUoIkF2ZXJhZ2UgU2NvcmUgb2YgV29yc3QgMTAgY3Vpc2luZXMiKSt4bGFiKCJDdWlzaW5lIERlc2NyaXB0aW9uIikreWxhYigiQXZlcmFnZSBTY29yZSIpK2dlb21fdGV4dChhZXMobGFiZWwgPSBzcHJpbnRmKCIlLjJmIiwgYXZlcmFnZV9zY29yZSkpLHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAxKSkKYGBgCgpJdCBpcyBpbnRlcmVzdGluZyB0aGF0IE5vdCBMaXN0ZWQvTm90IEFwcGxpY2FibGUgYW5kIE90aGVyIG1hZGUgaXQgdG8gdGhlIHRvcCAxMCBidXQgdGhlcmUgeW91IGhhdmUgaXQsIHRoZSBiZXN0IGFuZCB3b3JzdCBjdWlzaW5lcyBiYXNlZCB1cG9uIHRoZWlyIGF2ZXJhZ2Ugc2NvcmVzLgoKIyMgTWFpbiBBbmFseXNpcwpBZnRlciBhbmFseXppbmcgdGhlIHF1YWxpdHkgb2YgdGhlIGRhdGEgc2V0LCB3ZSBnb3QgcmlkIG9mIGRhdGEgaW4gd2hpY2ggdGhlIEJvcm91Z2ggaXMgbWlzc2luZyBhbmQgdGhlIHllYXIgaXMgYmVmb3JlIDIwMTMuCgpgYGB7cn0KVmlvbGF0aW9uc0RhdGEgPC0gVmlvbGF0aW9uc0RhdGEgJT4lIGZpbHRlciAoQk9STyAhPSAiTWlzc2luZyIpClZpb2xhdGlvbnNEYXRhIDwtIFZpb2xhdGlvbnNEYXRhICU+JSBmaWx0ZXIoCiAgYXMubnVtZXJpYyhmb3JtYXQoSU5TUEVDVElPTi5EQVRFICwgJyVZJykpID4gMjAxMikKYGBgCgpOb3cgd2UgY2FuIGdldCBhIGJldHRlciBwaWN0dXJlIG9mIHRoZSB0b3RhbCBudW1iZXIgb2YgaW5zcGVjdGlvbnMgYnkgeWVhciBhbmQgYm9yb3VnaC4KYGBge3J9CmJvcm91Z2hQbG90IDwtIGdncGxvdChWaW9sYXRpb25zRGF0YSwgYWVzKEJPUk8sZmlsbD1CT1JPKSkKYm9yb3VnaFBsb3QgKyBnZW9tX2JhcigpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKyAKICBmYWNldF93cmFwKH5mYWN0b3IoYXMubnVtZXJpYyggZm9ybWF0KElOU1BFQ1RJT04uREFURSAsICclWScpKSkpICsgCiAgZ2d0aXRsZSgiVG90YWwgSW5zcGVjdGlvbnMgZnJvbSAyMDEzLTIwMTcgaW4gZWFjaCBCb3JvdWdoIikgKwogIHhsYWIoIkJvcm91Z2giKSArIHlsYWIoIk51bWJlciBvZiBJbnNwZWN0aW9ucyIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQpgYGAKQWZ0ZXIgMjAxMywgdGhlcmUgc2VlbXMgdG8gYmUgYSBjb25zaXN0ZW50IGFtb3VudCBvZiBpbnNwZWN0aW9ucyBhY3Jvc3MgdGhlIHllYXJzLiBUaGUgbnVtYmVyIG9mIGluc3BlY3Rpb25zIGJ5IGJvcm91Z2ggYWxzbyBzZWVtcyB0byBtYWtlIHNlbnNlIHNpbmNlIHdlIGV4cGVjdCBNYW5oYXR0YW4gdG8gaGF2ZSB0aGUgbGFyZ2VzdCBudW1iZXIgb2YgcmVzdHVhcmFudHMuCgpOZXh0LCB3ZSB0b29rIGEgbG9vayBhdCB0aGUgZ3JhZGUgZGlzdHJpYnV0aW9uIGJ5IGJvcm91Z2guCmBgYHtyLGZpZy53aWR0aD04LGZpZy5oZWlnaHQ9M30KaW5zcGVjdGlvbl9ncmFkZXMgPC0gVmlvbGF0aW9uc0RhdGEgJT4lIHNlbGVjdCgtQ0FNSVMsIC1EQkEsIC1CVUlMRElORywgLVNUUkVFVCwgLVpJUENPREUsIC1QSE9ORSwgLUFDVElPTiwgLVZJT0xBVElPTi5DT0RFLCAtVklPTEFUSU9OLkRFU0NSSVBUSU9OLCAtQ1JJVElDQUwuRkxBRywgLVNDT1JFLCAtR1JBREUuREFURSwgLVJFQ09SRC5EQVRFLCAtSU5TUEVDVElPTi5UWVBFLC1DVUlTSU5FLkRFU0NSSVBUSU9OKQoKaW5zcGVjdGlvbl9ncmFkZXNfd295ZWFyPC1pbnNwZWN0aW9uX2dyYWRlcyAlPiUgc2VsZWN0KC1JTlNQRUNUSU9OLkRBVEUpCgppbnNwZWN0aW9uX2dyYWRlc193b3llYXIgPC0gaW5zcGVjdGlvbl9ncmFkZXNfd295ZWFyICU+JSBnYXRoZXIoa2V5LCB2YWx1ZSwgLUJPUk8pICU+JSBncm91cF9ieShCT1JPLCBrZXksIHZhbHVlKSAlPiUgdGFsbHkgJT4lIHNwcmVhZCh2YWx1ZSwgbiwgZmlsbCA9IDApCm5hbWVzKGluc3BlY3Rpb25fZ3JhZGVzX3dveWVhcilbbmFtZXMoaW5zcGVjdGlvbl9ncmFkZXNfd295ZWFyKT09ImtleSJdIDwtICJncmFkZSIKbmFtZXMoaW5zcGVjdGlvbl9ncmFkZXNfd295ZWFyKVtuYW1lcyhpbnNwZWN0aW9uX2dyYWRlc193b3llYXIpPT0iIl0gPC0gImBVbmtub3duIgppbnNwZWN0aW9uX2dyYWRlc193b3llYXIgPC0gaW5zcGVjdGlvbl9ncmFkZXNfd295ZWFyICU+JSBnYXRoZXIoa2V5LCB2YWx1ZSwgLUJPUk8sLWdyYWRlKQpSZWxGcmVxPC1mdW5jdGlvbihtKXsKICAgKChtICkvc3VtKG0pKQp9CgppbnNwZWN0aW9uX2dyYWRlc193b3llYXI8LWluc3BlY3Rpb25fZ3JhZGVzX3dveWVhciAlPiUgZ3JvdXBfYnkoQk9STyxncmFkZSkgJT4lCiAgICBtdXRhdGUocGVyY2VudGFnZSA9ICh2YWx1ZS9zdW0odmFsdWUpKSoxMDApCmdncGxvdChpbnNwZWN0aW9uX2dyYWRlc193b3llYXIsIGFlcyhCT1JPLCBwZXJjZW50YWdlICxmaWxsPSBrZXkpKSArIAogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgZ2d0aXRsZSgnJUdyYWRlIERpc3RyaWJ1dGlvbiBhY3Jvc3MgYm9yb3VnaHMnKSArIAogIGxhYnMoeCA9ICJCb3JvdWdoIiwgeSA9IlBlcmNlbnRhZ2UiKSArIGd1aWRlcyhmaWxsPWd1aWRlX2xlZ2VuZCh0aXRsZT0iR3JhZGUiKSkgICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMjUsIGZhY2UgPSAiYm9sZCIpKQoKYGBgCldlIGluaXRpYWxseSBoeXBvdGhlc2l6ZWQgdGhhdCB3ZSB3b3VsZCBzZWUgYSBwYXR0ZXJuIGluIGdyYWRlIGRpc3RyaWJ1dGlvbiBieSBib3JvdWdoLCBob3dldmVyLCB0aGUgcGxvdCBzaG93cyB1cyB0aGF0IGdyYWRlIGRpc3RyaWJ1dGlvbnMgYXJlIGFsbW9zdCB0aGUgc2FtZSByZWdhcmRsZXNzIG9mIGJvcm91Z2guCgpgYGB7cixmaWcud2lkdGg9OH0KCmluc3BlY3Rpb25fZ3JhZGVzX3llYXI8LWluc3BlY3Rpb25fZ3JhZGVzJT4lIG11dGF0ZSh5ZWFyPWZhY3Rvcihhcy5udW1lcmljKGZvcm1hdChJTlNQRUNUSU9OLkRBVEUgLCAnJVknKSkpKQppbnNwZWN0aW9uX2dyYWRlc195ZWFyPC1pbnNwZWN0aW9uX2dyYWRlc195ZWFyJT4lIHNlbGVjdCgtSU5TUEVDVElPTi5EQVRFKQogaW5zcGVjdGlvbl9ncmFkZXNfeWVhciA8LSBpbnNwZWN0aW9uX2dyYWRlc195ZWFyICU+JSBnYXRoZXIoa2V5LCB2YWx1ZSwgLUJPUk8sLXllYXIpICU+JSBncm91cF9ieShCT1JPLHllYXIsIGtleSwgdmFsdWUpICU+JSAKICAgdGFsbHkgJT4lIHNwcmVhZCh2YWx1ZSwgbiwgZmlsbCA9IDApIyAlPiUgZ2F0aGVyKGJsYWgsIC1CT1JPLCAta2V5KSAjc3VtbWFyaXplKGFwcm9wID0gQS8oQStCK0MrKSkKbmFtZXMoaW5zcGVjdGlvbl9ncmFkZXNfeWVhcilbbmFtZXMoaW5zcGVjdGlvbl9ncmFkZXNfeWVhcik9PSJrZXkiXSA8LSAiZ3JhZGUiCm5hbWVzKGluc3BlY3Rpb25fZ3JhZGVzX3llYXIpW25hbWVzKGluc3BlY3Rpb25fZ3JhZGVzX3llYXIpPT0iIl0gPC0gImBVbmtub3duIgppbnNwZWN0aW9uX2dyYWRlc195ZWFyIDwtIGluc3BlY3Rpb25fZ3JhZGVzX3llYXIgJT4lIGdhdGhlcihrZXksIHZhbHVlLCAtQk9STywtZ3JhZGUsLXllYXIpClJlbEZyZXE8LWZ1bmN0aW9uKG0pewogICAoKG0gKS9zdW0obSkpCiB9Cmluc3BlY3Rpb25fZ3JhZGVzX3llYXI8LWluc3BlY3Rpb25fZ3JhZGVzX3llYXIgJT4lIGdyb3VwX2J5KEJPUk8sZ3JhZGUseWVhcikgJT4lCiAgICBtdXRhdGUocGVyY2VudGFnZSA9ICh2YWx1ZS9zdW0odmFsdWUpKSoxMDApCmdncGxvdChpbnNwZWN0aW9uX2dyYWRlc195ZWFyLCBhZXMoQk9STywgcGVyY2VudGFnZSAsZmlsbD0ga2V5KSkgKyAKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgK2ZhY2V0X3dyYXAofnllYXIsbnJvdz0yLG5jb2w9MykrCiAgZ3VpZGVzKGZpbGw9Z3VpZGVfbGVnZW5kKHRpdGxlPSJHcmFkZSIpKSsKICBnZ3RpdGxlKCIlR3JhZGUgRGlzdHJpYnV0aW9uIGJ5IEJvcm91Z2ggYW5kIFllYXIiKSsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMjUsIGZhY2UgPSAiYm9sZCIpKQoKYGBgCgpBZGRpbmcgeWVhciB0byB0aGlzIGFuYWx5c2lzIHNob3dlZCB1cyBhbiBpbmNyZWFzZSBpbiB0aGUgcHJvcG9ydGlhbiBvZiBBcyBpbiBTdGF0ZW4gSXNsYW5kIGluIDIwMTUgYnV0IHNlZW1lZCBjb25zaXN0ZW50IHRocm91Z2hvdXQgdGhlIHJlc3Qgb2YgdGhlIHBsb3QuCgoKV2UgZGVjaWRlZCB0aGF0IGJvcm91Z2ggbWF5IGJlIHRvbyBnZW5lcmFsIGFuZCB0aHVzIGxvb2tlZCBhdCBncmFkZSBkaXN0cmlidXRpb24gYnkgemlwIGNvZGUgYWNyb3NzIHRoZSB2YXJpb3VzIHllYXJzLiBXZSB1c2VkIGEgaGVhdCBtYXAgdG8gZG8gc28uCmBgYHtyLGZpZy5oZWlnaHQ9MjAsZmlnLndpZHRoPTd9CmxpYnJhcnkodmlyaWRpcykKbm9uWWVhckRhdGFGb3JIZWF0TWFwPC1WaW9sYXRpb25zRGF0YSAlPiUgc2VsZWN0KEdSQURFLFpJUENPREUpCgpub25ZZWFyRGF0YUZvckhlYXRNYXAgPC0gbm9uWWVhckRhdGFGb3JIZWF0TWFwICU+JSBncm91cF9ieShHUkFERSxaSVBDT0RFKSAlPiUgdGFsbHkgClJlbEZyZXE8LWZ1bmN0aW9uKG0pewogICAoKG0gKS9zdW0obSkpCn0KCm5vblllYXJEYXRhRm9ySGVhdE1hcDwtICBub25ZZWFyRGF0YUZvckhlYXRNYXAgJT4lIGdyb3VwX2J5KFpJUENPREUpJT4lCiAgICBtdXRhdGUocGVyY2VudGFnZSA9IChuL3N1bShuKSkqMTAwKQoKZ2dwbG90KG5vblllYXJEYXRhRm9ySGVhdE1hcCwgYWVzKEdSQURFLCAKICAgICAgICAgICAgICAgICAgICAgICAgWklQQ09ERSwgZmlsbCA9IHBlcmNlbnRhZ2UpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcygpICsKICBnZ3RpdGxlKCJQZXJjZW50YWdlIEdyYWRlIERpc3RyaWJ1dGlvbiBieSBaaXBjb2RlXG4gIikreWxhYigiWmlwIENvZGUiKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDM1KSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3VyPSJncmV5MjAiLHNpemU9MTUsYW5nbGU9MCxoanVzdD0uNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmp1c3Q9LjUsZmFjZT0icGxhaW4iKSwgCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoY29sb3VyPSJncmV5MjAiLHNpemU9MTAsYW5nbGU9MCxoanVzdD0xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZqdXN0PTAsZmFjZT0icGxhaW4iKSwgIAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChjb2xvdXI9ImdyZXkyMCIsc2l6ZT0yNSxhbmdsZT0wLGhqdXN0PS41LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2anVzdD0wLGZhY2U9InBsYWluIiksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KGNvbG91cj0iZ3JleTIwIixzaXplPTMwLGFuZ2xlPTkwLGhqdXN0PS41LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2anVzdD0uNSxmYWNlPSJwbGFpbiIpLAogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTIwKSwKICAgICAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0yMCksIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMSwgJ2luJykpCmBgYApJdCBpcyBpbnRlcmVzdGluZyB0byBub3RlIHRoYXQgbWlzc2luZyBncmFkZXMgYXJlIHByb2JhYmx5IHRoZSBtb3N0IHByb2JhYmxlIGluIGdlbmVyYWwuIFRoZXJlIGFyZSBzb21lIHppcCBjb2RlcyB3aXRoIGFsbW9zdCBubyBtaXNzaW5nIGdyYWRlcy4gVGhlIHJlc3R1YXJhbnRzIGluIHRob3NlIHNhbWUgemlwIGNvZGVzIHNlZW0gdG8gbW9zdGx5IGhhdmUgYWxsIEEgZ3JhZGVzLiBBbm90aGVyIGludGVyZXN0aW5nIHBvaW50IGlzIDEgemlwIGNvZGUgd2hpY2ggaGFzIGFsbCBOb3QgWWV0IEdyYWRlZCBncmFkZXMuIEluIGdlbmVyYWwgbWlzc2luZyBncmFkZXMgYW5kIGdyYWRlcyBvZiBhbiBBIHNlZW0gdGhlIG1vc3QgY29tbW9uLiBUaGlzIGlzIGZvbGxvd2VkIGJ5IGdyYWRlcyBvZiBhIEIuCgpUaGUgbmV4dCBwYXJ0IG9mIG91ciBhbmFseXNpcyB3YXMgdG8gbG9vayBhdCB0aGUgYXZlcmFnZSBzY29yZXMgb2YgZWFjaCBjdWlzaW5lLiBXaXRoIHRoaXMgaW5mb3JtYXRpb24sIHdlIGNhbiBoZWxwIGNvbnN1bWVycyBzZWUgd2hhdCBraW5kcyBvZiBlc3RhYmxpc2htZW50cyBoYXZlIHRoZSBiZXN0IGFuZCB3b3JzdCBzY29yZXMgb24gYXZlcmFnZS4gVGhpcyBpbiB0ZXJtIGNhbiBoZWxwIGEgY29uc3VtZXIgY2hvb3NlIGEgdHlwZSBvZiBjdWlzaW5lIHdoZW4gdGhleSBhcmUgaHVuZ3J5LgoKYGBge3IsZmlnLmhlaWdodD0yMCxmaWcud2lkdGg9NX0KYXZlcmFnZV9zY29yZXMgPC0gVmlvbGF0aW9uc0RhdGEgJT4lIHNlbGVjdCgtQ0FNSVMsIC1EQkEsIC1CVUlMRElORywgLVNUUkVFVCwgLVpJUENPREUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC1QSE9ORSwgLUlOU1BFQ1RJT04uREFURSwgLUFDVElPTiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLVZJT0xBVElPTi5DT0RFLCAtVklPTEFUSU9OLkRFU0NSSVBUSU9OLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtQ1JJVElDQUwuRkxBRywgLUdSQURFLCAtR1JBREUuREFURSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLVJFQ09SRC5EQVRFLCAtSU5TUEVDVElPTi5UWVBFKQoKYXZlcmFnZV9jdWlzaW5lIDwtIGF2ZXJhZ2Vfc2NvcmVzICU+JSAgZ3JvdXBfYnkoQ1VJU0lORS5ERVNDUklQVElPTikgJT4lIG5hLm9taXQgJT4lCiAgZHBseXI6OnN1bW1hcml6ZShhdmVyYWdlX3Njb3JlID0gbWVhbihTQ09SRSkpCmF2ZXJhZ2VfY3Vpc2luZSA8LSBhcnJhbmdlKGF2ZXJhZ2VfY3Vpc2luZSwgLWF2ZXJhZ2Vfc2NvcmUpCgpnZ3Bsb3QoYXZlcmFnZV9jdWlzaW5lLCBhZXMocmVvcmRlcih4ID0gQ1VJU0lORS5ERVNDUklQVElPTiwgLS1hdmVyYWdlX3Njb3JlKSwgYXZlcmFnZV9zY29yZSxmaWxsPSJCbHVlIikpICsgZ2VvbV9iYXIoc3RhdD0naWRlbnRpdHknKSArIGNvb3JkX2ZsaXAoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykrZ2d0aXRsZSgiQXZlcmFnZSBTY29yZXMgQWNyb3NzIEN1aXNpbmVzIikreGxhYigiQ3Vpc2luZSBEZXNjcmlwdGlvbiIpK3lsYWIoIkF2ZXJhZ2UgU2NvcmUiKStnZW9tX3RleHQoYWVzKGxhYmVsID0gc3ByaW50ZigiJS4yZiIsIGF2ZXJhZ2Vfc2NvcmUpKSxwb3NpdGlvbj1wb3NpdGlvbl9zdGFjayh2anVzdD0wLjUpLHZqdXN0ID0gMC41LCBzaXplID0gMykgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAyNSksIAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChjb2xvdXI9ImdyZXkyMCIsc2l6ZT0yMCxhbmdsZT0wLGhqdXN0PS41LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2anVzdD0wLGZhY2U9InBsYWluIiksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KGNvbG91cj0iZ3JleTIwIixzaXplPTIwLGFuZ2xlPTkwLGhqdXN0PS41LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2anVzdD0uNSxmYWNlPSJwbGFpbiIpKQpgYGAKCmBgYHtyfQp3b3JzdDEwIDwtIGF2ZXJhZ2VfY3Vpc2luZVsxOjEwLF0KCmdncGxvdCh3b3JzdDEwLCBhZXMocmVvcmRlcih4ID0gQ1VJU0lORS5ERVNDUklQVElPTiwgLS1hdmVyYWdlX3Njb3JlKSwgYXZlcmFnZV9zY29yZSwgZmlsbCA9IENVSVNJTkUuREVTQ1JJUFRJT04pKSArIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykgKyBjb29yZF9mbGlwKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpK2dndGl0bGUoIkF2ZXJhZ2UgU2NvcmUgb2YgV29yc3QgMTAgY3Vpc2luZXMiKSt4bGFiKCJDdWlzaW5lIERlc2NyaXB0aW9uIikreWxhYigiQXZlcmFnZSBTY29yZSIpK2dlb21fdGV4dChhZXMobGFiZWwgPSBzcHJpbnRmKCIlLjJmIiwgYXZlcmFnZV9zY29yZSkpLHBvc2l0aW9uPXBvc2l0aW9uX3N0YWNrKHZqdXN0PTAuNSkpCmBgYAoKYGBge3J9Cm4gPC0gbnJvdyhhdmVyYWdlX2N1aXNpbmUpCmJlc3QxMCA8LSBhdmVyYWdlX2N1aXNpbmVbKG4tIDEwKTpuLF0KYmVzdDEwIDwtIGJlc3QxMFtvcmRlcihiZXN0MTAkYXZlcmFnZV9zY29yZSksXQoKZ2dwbG90KGJlc3QxMCwgYWVzKHJlb3JkZXIoeCA9IENVSVNJTkUuREVTQ1JJUFRJT04sIC1hdmVyYWdlX3Njb3JlKSwgYXZlcmFnZV9zY29yZSwgZmlsbCA9IENVSVNJTkUuREVTQ1JJUFRJT04pKSArIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykgKyBjb29yZF9mbGlwKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpK2dndGl0bGUoIkF2ZXJhZ2UgU2NvcmUgb2YgdG9wIDEwIGN1aXNpbmVzIikreGxhYigiQ3Vpc2luZSBEZXNjcmlwdGlvbiIpK3lsYWIoIkF2ZXJhZ2UgU2NvcmUiKStnZW9tX3RleHQoYWVzKGxhYmVsID0gc3ByaW50ZigiJS4yZiIsIGF2ZXJhZ2Vfc2NvcmUpKSxwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMSksI3Bvc2l0aW9uPXBvc2l0aW9uX3N0YWNrKHZqdXN0PTAuNSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmp1c3QgPSAwLjUsIHNpemUgPSA0KQpgYGAKCkl0IGlzIGludGVyZXN0aW5nIHRoYXQgdGhlIG15c3RlcmlvdXMgIm5vdCBhcHBsaWNhYmxlIiIgY3Vpc2luZSBpcyBvbmUgb2YgdGhlIGJlc3QhCgpJbiB0aGlzIG5leHQgcGxvdCwgd2Ugd2FudGVkIHRvIHNlZSBpZiB0aGUgYXZlcmFnZSBzY29yZSBzaWduaWZpY2FudGx5IGRpZmZlcmVkIGJ5IEJvcm91Z2guCmBgYHtyfQphdmVyYWdlX2Jvcm91Z2ggPC0gYXZlcmFnZV9zY29yZXMgJT4lICBncm91cF9ieShCT1JPKSAlPiUgbmEub21pdCAlPiUKICBkcGx5cjo6c3VtbWFyaXplKGF2ZXJhZ2Vfc2NvcmUgPSBtZWFuKFNDT1JFKSkKCmdncGxvdChhdmVyYWdlX2Jvcm91Z2gsIGFlcyhyZW9yZGVyKHggPSBCT1JPLCAtLWF2ZXJhZ2Vfc2NvcmUpLCBhdmVyYWdlX3Njb3JlLCBmaWxsID0gQk9STykpICsgZ2VvbV9iYXIoc3RhdD0naWRlbnRpdHknKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykrZ2d0aXRsZSgiQXZlcmFnZSBTY29yZSBhY3Jvc3MgYm9yb3VnaHMiKSt4bGFiKCJCb3JvdWdoIikreWxhYigiQXZlcmFnZSBTY29yZSIpK2dlb21fdGV4dChhZXMobGFiZWwgPSBzcHJpbnRmKCIlLjJmIiwgYXZlcmFnZV9zY29yZSkpLHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmp1c3QgPSAtMC41LCBzaXplID0gMykKYGBgCgpBbHRob3VnaCB3ZSBzZWUgYSBzbGlnaHQgZGlmZmVyZW5jZSwgdGhlIGJvcm91Z2hzIGhhdmUgYXBwcm94aW1hdGVseSB0aGUgc2FtZSBhdmVyYWdlIHNjb3JlLiBTdGF0ZW4gSXNsYW5kIG1heSBiZSB0aGUgb25lIGV4Y2VwdGlvbi4gV2UgYWxzbyBmYWNldGVkIGJ5IHllYXIgdG8gc2VlIGlmIHRoYXQgbWFkZSBhIGRpZmZlcmVuY2UsIGJ1dCB0aGUgcmVzdWx0cyB3ZXJlIGVzc2VudGlhbGx5IHRoZSBzYW1lIGFzIGluIHRoZSBwbG90IGFib3ZlLgoKU28gZmFyIHdlIGxvb2tlZCBhdCBncmFkZXMgYW5kIHNjb3JlcyBidXQgbm90IHRoZSBhY3R1YWwgdmlvbGF0aW9ucy4gV2hhcmUgYXJlIHRoZSB0b3AgdmlvbGF0aW9ucyByZXN0YXVyYW50cyB1c3VhbGx5IGZhY2U/CmBgYHtyLGZpZy53aWR0aD0xMn0KCnZpb2xhdGlvbnM8LSBWaW9sYXRpb25zRGF0YSAlPiUgc2VsZWN0KC1DQU1JUywgLURCQSwgLUJVSUxESU5HLCAtU1RSRUVULCAtWklQQ09ERSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLVBIT05FLCAtSU5TUEVDVElPTi5EQVRFLCAtQUNUSU9OLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtVklPTEFUSU9OLkNPREUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC1DUklUSUNBTC5GTEFHLCAtR1JBREUsIC1HUkFERS5EQVRFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtUkVDT1JELkRBVEUsIC1JTlNQRUNUSU9OLlRZUEUpCgp2cyA8LSB2aW9sYXRpb25zICU+JSAgZ3JvdXBfYnkoVklPTEFUSU9OLkRFU0NSSVBUSU9OKSAlPiUgbmEub21pdCAlPiUKICBkcGx5cjo6c3VtbWFyaXplKGNvdW50ID0gbigpKQp2cyA8LSBhcnJhbmdlKHZzLCAtY291bnQpCgp0b3B2aW9sYXRpb25zIDwtIHZzWzE6MTAsXQoKbGlicmFyeShzdHJpbmdyKQp0b3B2aW9sYXRpb25zJHZpb2wgPSBzdHJfd3JhcCh0b3B2aW9sYXRpb25zJFZJT0xBVElPTi5ERVNDUklQVElPTiwgd2lkdGggPSAxNSkKCmdncGxvdCh0b3B2aW9sYXRpb25zLCBhZXMocmVvcmRlcih2aW9sLCAtY291bnQpLCBjb3VudCxmaWxsPXZpb2wpKSArIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpK2dndGl0bGUoIlRvcCAxMCB2aW9sYXRpb25zIFxuIikreGxhYigiVmlvbGF0aW9uIGRlc2NyaXB0aW9uIikreWxhYigiY291bnQiKStnZW9tX3RleHQoYWVzKGxhYmVsID0gc3ByaW50ZigiJS4wZiIsIGNvdW50KSksCiBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDcpICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gNDAsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChjb2xvdXI9ImdyZXkyMCIsc2l6ZT0yMCxhbmdsZT0wLGhqdXN0PS41LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2anVzdD0uNSxmYWNlPSJwbGFpbiIpLCAKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChjb2xvdXI9ImdyZXkyMCIsc2l6ZT0zMCxhbmdsZT0wLGhqdXN0PTEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmp1c3Q9MCxmYWNlPSJwbGFpbiIpLCAgCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KGNvbG91cj0iZ3JleTIwIixzaXplPTMwLGFuZ2xlPTAsaGp1c3Q9LjUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZqdXN0PTAsZmFjZT0icGxhaW4iKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoY29sb3VyPSJncmV5MjAiLHNpemU9MzAsYW5nbGU9OTAsaGp1c3Q9LjUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZqdXN0PS41LGZhY2U9InBsYWluIikpCmBgYAoKV2UgYWxzbyBoeXBvdGhlc2l6ZWQgdGhhdCB0aGUgdHlwZSBvZiBpbnNwZWN0aW9ucyB3b3VsZCBnZW5lcmFsbHkgdmFyeSBieSBCb3JvdWdoIGJ1dCBldmVuIHRoaXMgd2FzIG1vc3RseSBjb25zaXN0ZW50LgpgYGB7cixmaWcuaGVpZ2h0PTYsIG1lc3NhZ2UgPSBGQUxTRX0KCiBsaWJyYXJ5KHZpcmlkaXMpCm1vczwtIFZpb2xhdGlvbnNEYXRhICU+JSBzZWxlY3QoLUNBTUlTLCAtREJBLCAtQlVJTERJTkcsIC1TVFJFRVQsIC1aSVBDT0RFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC1QSE9ORSwgLUFDVElPTiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtVklPTEFUSU9OLkNPREUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLUNSSVRJQ0FMLkZMQUcsIC1HUkFERSwgLUdSQURFLkRBVEUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLVJFQ09SRC5EQVRFLCAtU0NPUkUsIC1DVUlTSU5FLkRFU0NSSVBUSU9OLCAtVklPTEFUSU9OLkRFU0NSSVBUSU9OKQptb3MkeWVhciA8LSBmYWN0b3IoYXMubnVtZXJpYyggZm9ybWF0KG1vcyRJTlNQRUNUSU9OLkRBVEUgLCAnJVknKSkpCm1vcyA8LSBtb3MgJT4lIHNlbGVjdCgtSU5TUEVDVElPTi5EQVRFKQoKYXZlcmFnZV9tb3MgPC0gbW9zICU+JSBncm91cF9ieShCT1JPLCB5ZWFyLCBJTlNQRUNUSU9OLlRZUEUpICU+JSB0YWxseSAlPiUKIGdyb3VwX2J5KEJPUk8sIHllYXIpICAlPiUgCiBtdXRhdGUocGVyY2VudGFnZSA9IG4gLyBzdW0obikpCiNhdmVyYWdlX2N1aXNpbmUgPC0gYXJyYW5nZShhdmVyYWdlX2N1aXNpbmUsIC1hdmVyYWdlX3Njb3JlKQoKbGlicmFyeSh2Y2QpCmF2ZXJhZ2VfbW9zIDwtIGF2ZXJhZ2VfbW9zICU+JSBzZWxlY3QoLW4pCgoKZ2dwbG90KGF2ZXJhZ2VfbW9zLCBhZXMoQk9STywgCiAgICAgICAgICAgICAgICAgICAgICAgSU5TUEVDVElPTi5UWVBFLCBmaWxsID0gcGVyY2VudGFnZSkpICsKIGdlb21fdGlsZSgpICsKIHNjYWxlX2ZpbGxfdmlyaWRpcygpICsKICNmYWNldF93cmFwKH5CT1JPKQogZ2d0aXRsZSgiQXZlcmFnZSBWaW9sYXRpb24gU2NvcmUgYnkgWmlwIENvZGVcbiAiKSsgeGxhYignQm9yb3VnaCcpICsKIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDMwLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3VyPSJncmV5MjAiLHNpemU9MjAsYW5nbGU9MCxoanVzdD0uNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmp1c3Q9LjUsZmFjZT0icGxhaW4iKSwgCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoY29sb3VyPSJncmV5MjAiLHNpemU9MTUsYW5nbGU9MCxoanVzdD0xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZqdXN0PTAsZmFjZT0icGxhaW4iKSwgIAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChjb2xvdXI9ImdyZXkyMCIsc2l6ZT0yNSxhbmdsZT0wLGhqdXN0PS41LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2anVzdD0wLGZhY2U9InBsYWluIiksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KGNvbG91cj0iZ3JleTIwIixzaXplPTI1LGFuZ2xlPTkwLGhqdXN0PS41LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2anVzdD0uNSxmYWNlPSJwbGFpbiIpLAogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTMwKSwKICAgICAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0yMCksIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMSwgJ2luJykpCgpgYGAKT25lIHRoaW5nIHdlIG5vdGljZWQgd2FzIHRoYXQgU3RhdGVuIGlzbGFuZCBoYXMgdGhlIGxlYXN0IEN5Y2xlIEluc3BlY3Rpb24vSW5pdGlhbCBJbnNwZWN0aW9uLiBNYXliZSB0aGV5IHdlcmUgb3BlbmluZyB1cCB0aGUgbGVhc3QgbmV3IHJlc3R1YXJhbnRzLiBUaGV5IGFsc28gaGFkIHRoZSBtb3N0IGN5Y2xlIGluc3BlY3Rpb24vcmUtaW5zcGVjdGlvbiBwZXJjZW50YWdlLiBBcyB3ZSdsbCBzZWUgaW4gdGhlIG1hcHMgYmVsb3csIHRob3NlIHJlaW5zcGVjdGlvbnMgbWF5IGhhdmUgaGVscGVkIHJlc3RhdXJhbnRzIGluIHN0YXRlbiBpc2xhbmQgaW1wcm92ZSB0aGVpciBncmFkZXMgYW5kIHNjb3Jlcy4KCgpPbmUgZmFjdG9yIG9mIG91ciBkYXRhIHNldCB3ZSBoYXZlbid0IGRpc2N1c3NlZCB0b28gbXVjaCBpcyB0aGUgQ3JpdGljYWwgZmxhZywgc2F5aW5nIHdoZXRoZXIgb3Igbm90IGEgdmlvbGF0aW9uIGlzIGNyaXRpY2FsLiBPbmNlIGFnYWluLCB3ZSBhc3N1bWVkIHRoYXQgb3ZlciB0aGUgeWVhcnMgYW5kIGFjcm9zcyBkaWZmZXJlbnQgYm9yb3VnaHMsIHRoZXJlIHdvdWxkIGJlIHNvbWUgcGF0dGVybiBpbiBjcml0aWNhbCB2aW9sYXRpb25zLiBBcyBjYW4gYmUgaW5mZXJyZWQgZnJvbSB0aGUgZm9sbG93aW5nIHBsb3QsIHRoZXJlIGlzIG5vdC4KYGBge3IsZmlnLndpZHRoPTV9CkNyaXRpY2FsaXR5RGF0YTwtIFZpb2xhdGlvbnNEYXRhICU+JSBzZWxlY3QoQk9STyxJTlNQRUNUSU9OLkRBVEUsQ1JJVElDQUwuRkxBRykgJT4lIG5hLm9taXQoKQpDcml0aWNhbGl0eURhdGEkeWVhcjwtZmFjdG9yKGFzLm51bWVyaWMoIGZvcm1hdChDcml0aWNhbGl0eURhdGEkSU5TUEVDVElPTi5EQVRFICwgJyVZJykpKQojQ3JpdGljYWxpdHlEYXRhIDwtIENyaXRpY2FsaXR5RGF0YSAlPiVzZWxlY3QoLUlOU1BFQ1RJT04uREFURSkKCkNyaXRpY2FsaXR5RGF0YSA8LSBDcml0aWNhbGl0eURhdGEgJT4lc2VsZWN0KC1JTlNQRUNUSU9OLkRBVEUpICU+JSBnYXRoZXIoa2V5LCB2YWx1ZSwgLUJPUk8sIC15ZWFyKSAlPiUgZ3JvdXBfYnkoQk9STywgeWVhcixrZXksdmFsdWUpICU+JSB0YWxseSAlPiUgc3ByZWFkKHZhbHVlLCBuLCBmaWxsID0gMCkKbmFtZXMoQ3JpdGljYWxpdHlEYXRhKVs1XSA8LSAiTm90QXBwbGljYWJsZSIKbmFtZXMoQ3JpdGljYWxpdHlEYXRhKVs2XSA8LSAiTm9uQ3JpdGljYWwiCgpDcml0aWNhbGl0eURhdGFQZXJjZW50YWdlPC1Dcml0aWNhbGl0eURhdGEgJT4lIHN1bW1hcml6ZShjcml0aWNhbFBlcmNlbnQgPSBDcml0aWNhbC8oQ3JpdGljYWwrTm90QXBwbGljYWJsZStOb25Dcml0aWNhbCksIE5vdEFwcGxpY2FibGVQZXJjZW50ID0gTm90QXBwbGljYWJsZS8oQ3JpdGljYWwrTm90QXBwbGljYWJsZStOb25Dcml0aWNhbCksIE5vbkNyaXRpY2FsUGVyY2VudCA9IE5vbkNyaXRpY2FsLyhDcml0aWNhbCtOb3RBcHBsaWNhYmxlK05vbkNyaXRpY2FsKSkjCgpuYW1lcyhDcml0aWNhbGl0eURhdGFQZXJjZW50YWdlKVs0XSA8LSAiQ3JpdGljYWwiCm5hbWVzKENyaXRpY2FsaXR5RGF0YVBlcmNlbnRhZ2UpWzVdIDwtICJOb3RBcHBsaWNhYmxlIgpuYW1lcyhDcml0aWNhbGl0eURhdGFQZXJjZW50YWdlKVs2XSA8LSAiTm9uQ3JpdGljYWwiCkNyaXRpY2FsaXR5RGF0YVBlcmNlbnRhZ2U8LUNyaXRpY2FsaXR5RGF0YVBlcmNlbnRhZ2UlPiUgZ2F0aGVyKGtleTEsIHZhbHVlLCAtQk9STywgLXllYXIsLWtleSkKCgpnZ3Bsb3QoQ3JpdGljYWxpdHlEYXRhUGVyY2VudGFnZSwgYWVzKEJPUk8sIHZhbHVlLCBmaWxsID0ga2V5MSkpICsgCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsgCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikrIHhsYWIoIkJPUk9VR0giKSt5bGFiKCJDUklUSUNBTElUWSBQRVJDRU5UQUdFUyIpICt0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsgZ2d0aXRsZSgiR3JvdXBlZCBiYXJjaGFydCBvZiBncmFkZSBmcmVxdWVuY3kgaW4gZWFjaCBjdWlzaW5lIGNhdGVnb3J5IFxuIHdoZW4gdGhlcmUgd2FzIGEgdmlvbGF0aW9uIHJlcG9ydGVkIikrZmFjZXRfd3JhcCh+eWVhcixucm93PTIsbmNvbD0zKStndWlkZXMoZmlsbD1ndWlkZV9sZWdlbmQodGl0bGU9IkNyaXRpY2FsaXR5IFxuIGNhdGVnb3J5IikpCgpgYGAKV2UgYXR0ZW1wdGVkIHRvIGZ1cnRoZXIgYW5hbHl6ZSB0aGUgY3JpdGljYWwgZmxhZyBidXQgd2UgbGVhcm5lZCB0aGF0IHRoZSBjcml0aWNhbCBmbGFnIGRpZCBub3QgaGF2ZSB0b28gbXVjaCBvZiBhbiBpbXBhY3Qgb24gZ3JhZGUgb3Igc2NvcmVzLiBUaGVyZSB3YXMgYSBsb3Qgb2YgZGF0YSB3aXRoIGEgZ3JhZGUgb2YgQSBidXQgYSBDcml0aWNhbCBmbGFnIHdoaWxlIG90aGVycyB3aXRoIGEgdmVyeSBoaWdoIHNjb3JlIGJ5IG5vIGNyaXRpY2FsIGZsYWcuCgojIyBWSVNVQUxJU0lORyBEQVRBIFdJVEggTUFQUwoKRm9yIHRoZSByZW1haW5kZXIgb2YgdGhlIGFuYWx5c2lzLCB3ZSB3aWxsIGJlIHdvcmtpbmcgd2l0aCBUYWJsZWF1LiBXZSBjaG9zZSBUYWJsZWF1IGJlY2F1c2Ugd2Ugd2FudGVkIHRvIHBsb3QgZWxlZ2VudCBtYXBzLiBUYWJsZWF1IHByb3ZpZGVzIGEgc2ltcGxlciBhbmQgbW9yZSBlbGVnZW50IHdheSB0byBkbyBzbyB0aGFuIGluIFIuCgorIFBsZWFzZSBub3RlIHRoYXQgdGhlcmUgaXMgYSB0dXRvcmlhbCBmb3IgZGVyaXZpbmcgbWFwcyBmcm9tIGdlbmVyYXRlZCBkYXRzZXQgaW4gVGFibGVhdSBpbiB0aGUgZ2l0aHViIHJlcG9zaXRvcnkgYXMgQ2hvcm9wbGV0aE1hcHMuaHRtbCB1bmRlciB0aGUgIkZpbmFsUmVwb3J0IiBmb2xkZXIgaW4gdGhlIEdpdGh1YiBSZXBvc2l0b3J5LiBJdCB3YXMgY3JlYXRlZCBieSBvdXIgdmVyeSBvd24gTGFrc2h5YSBHYXJnLgoKVGhlIGNvZGUgY2h1bmtzIHdpbGwgZ2VuZXJhdGUgdGhlIGRhdGEgdGhhdCB3ZSB1cGxvYWQgdG8gVGFibGVhdS4gSW4gdGhlIGZvbGxvd2luZyBhbmFseXNpcywgd2UgZXhwbG9yZSB0aGUgcHJvcG9ydGlvbiBvZiBBIGdyYWRlcyBiYXNlZCBvbiBuZWlnaGJvcmhvb2RzIG92ZXIgdGhlIHllYXJzLgpgYGB7cixmaWcuaGVpZ2h0PTE1fQojIyMjI0dFTkVSQVRFIEdSQURFIFBST1BPRVJUSU9OIERBVEFTRVQgQlkgWUVBUiBGT1IgVEFCTEVBVSBQTE9UVElORwp6aXBkYXRhPC0gVmlvbGF0aW9uc0RhdGEgJT4lIHNlbGVjdCgtQ0FNSVMsIC1EQkEsIC1CVUlMRElORywgLVNUUkVFVCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLVBIT05FLCAtQUNUSU9OLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtVklPTEFUSU9OLkNPREUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC1DUklUSUNBTC5GTEFHLCAtU0NPUkUsIC1HUkFERS5EQVRFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtUkVDT1JELkRBVEUsIC1JTlNQRUNUSU9OLlRZUEUsIC1WSU9MQVRJT04uREVTQ1JJUFRJT04sIC1DVUlTSU5FLkRFU0NSSVBUSU9OLCAtQk9STykgJT4lIG5hLm9taXQoKQp6aXBkYXRhJHllYXI8LWZhY3Rvcihhcy5udW1lcmljKCBmb3JtYXQoemlwZGF0YSRJTlNQRUNUSU9OLkRBVEUgLCAnJVknKSkpCnppcGRhdGFHcmFkZVBlcmNlbnRhZ2UgPC0gemlwZGF0YSAlPiVzZWxlY3QoLUlOU1BFQ1RJT04uREFURSkgJT4lZ2F0aGVyKGtleSwgdmFsdWUsIC1aSVBDT0RFLCAteWVhcikgJT4lIGdyb3VwX2J5KFpJUENPREUsIHllYXIsIGtleSwgdmFsdWUpICU+JQogIHRhbGx5ICU+JSBzcHJlYWQodmFsdWUsIG4sIGZpbGwgPSAwKSAlPiUgc3VtbWFyaXplKGFwcm9wID0gQS8oQStCK0MpLCBicHJvcCA9IEIvKEErQitDKSwgY3Byb3AgPSBDLyhBK0IrQykgKQoKIyBnZ3Bsb3QoemlwZGF0YUdyYWRlUGVyY2VudGFnZSwgYWVzKHJlb3JkZXIoeCA9IFpJUENPREUsIC0tYXByb3ApLCBhcHJvcCkpICsgZ2VvbV9iYXIoc3RhdD0naWRlbnRpdHknKSArIAojICAgY29vcmRfZmxpcCgpICsgZmFjZXRfd3JhcCh+eWVhcikKIyAgIHRoZW1lKCBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDMpKQpgYGAKCiMjIyBQUk9QT1JUSU9OUyBPRiBBIEdSQURFUyBBQ1JPU1MgVEhFIFlFQVJTCgpPdmVyIHRoZSB5ZWFycywgdGhlIHByb3BvcnRpb24gb2YgQSdzIGhhcyBpbmNyZWFzZWQsIGFzIHJlcHJlc2VudGVkIGJ5IHRoZSBkYXJrZXIgcmVkIHNoYWRpbmcgb2YgdGhlIG1hcC4gSXQgaXMgdG9vIGVhcmx5IHRvIHN0dWR5IDIwMTcgaW4gZGVwdGggZHVlIHRvIHRoZSBsaW1pdGVkIG51bWJlciBvZiBpbnNwZWN0aW9ucyBzbyBmYXIuIE5vdGUgdGhhdCAyMDE3IGRhdGEgbWFwcyBhcmUgb25seSBmb3IgdGhlIGZpcnN0IHF1YXJ0ZXIgYW5kIGFyZSBsaWtlbHkgdG8gY2hhbmdlLgoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdBcHJvcDIwMTMucG5nJykKYGBgCgpgYGB7cn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoJ0Fwcm9wMjAxNC5wbmcnKQpgYGAKCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygnQXByb3AyMDE1LnBuZycpCmBgYAoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdBcHJvcDIwMTYucG5nJykKYGBgCgpgYGB7cn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoJ0Fwcm9wMjAxNy5wbmcnKQpgYGAKCiMjIyBQUk9QT1JUSU9OUyBPRiBCIEdSQURFUyBBQ1JPU1MgVEhFIFlFQVJTCgpMZXQncyBsb29rIGF0IGhvdyB0aGUgcHJvcG9ydGlvbiBvZiBCJ3MgY2hhbmdlLiBXZSBjYW4gc2VlIHRoYXQgb3ZlciB0aGUgeWVhcnMsIHRoZXJlIHNlZW0gdG8gYmUgbGVzcyBCJ3Mgd2hpY2ggbWFrZXMgc2Vuc2Ugc2luY2Ugb3ZlciB0aGUgeWVhcnMgd2UgaGF2ZSBtb3JlIGdyYWRlcyBvZiBhbiBBLiBOb3RlIHRoYXQgMjAxNyBkYXRhIG1hcHMgYXJlIG9ubHkgZm9yIHRoZSBmaXJzdCBxdWFydGVyIGFuZCBhcmUgbGlrZWx5IHRvIGNoYW5nZS4KCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygnQnByb3AxLnBuZycpCmBgYAoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdCcHJvcDIucG5nJykKYGBgCgpgYGB7cn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoJ0Jwcm9wMy5wbmcnKQpgYGAKCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygnQnByb3A0LnBuZycpCmBgYAoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdCcHJvcDUucG5nJykKYGBgCgoKIyMjIEFWRVJBR0UgU0NPUkVTIEJZIFpJUENPREUgT1ZFUiBUSEUgWUVBUlMKCkxldCdzIGxvb2sgYXQgdGhlIGF2ZXJhZ2Ugc2NvcmVzIGJ5IG5laWdoYm9yaG9vZC4gV2UgaGF2ZSBhbHJlYWR5IGRvbmUgc28gYnkgY3Vpc2luZS4KT3ZlciB0aGUgeWVhcnMsIHdlIGhhdmUgYSBsb3dlciBBdmVyYWdlIFNjb3JlLiBUaGVzZSBOWUMgaW5zcGVjdGlvbnMgbXVzdCBiZSBwcm9tcHRpbmcgcmVzdHVyYW50cyB0byBpbXByb3ZlIHRoZWlyIGZhY2lsaXRpZXMgYW5kIGZvbGxvdyByZWd1bGF0aW9ucyEKCk5vdGUgdGhhdCAyMDE3IGRhdGEgbWFwcyBhcmUgb25seSBmb3IgdGhlIGZpcnN0IHF1YXJ0ZXIgYW5kIGFyZSBsaWtlbHkgdG8gY2hhbmdlLgoKCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygnQXZnU2NvcmUyMDEzLnBuZycpCmBgYAoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdBdmdTY29yZTIwMTQucG5nJykKYGBgCgpgYGB7cn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoJ0F2Z1Njb3JlMjAxNS5wbmcnKQpgYGAKCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygnQXZnU2NvcmUyMDE2LnBuZycpCmBgYAoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdBdmdTY29yZTIwMTcucG5nJykKYGBgCgojIyMgQVZFUkFHRSBSRS1JTlNQRUNUSU9OUyBPVkVSIFRIRSBZRUFSUwoKV2UgZGVjaWRlZCB0byBzZWUgd2hpY2ggYXJlYXMgaGFkIHRoZSBoaWdoZXN0IGF2ZXJhZ2UgbnVtYmVyIG9mIHJlaW5zcGVjdGlvbnMuIE1heWJlIHRoaXMgY2FuIGV4cGxhaW4gdGhlIGJldHRlciBzY29yZXMgYW5kIGdyYWRlcyBvdmVyIHRpbWUuIEludGVyZXN0aW5nbHkgZW5vdWdoLCB0aGUgYXJlYXMgd2l0aCB0aGUgbW9zdCByZWluc3BlY3Rpb25zIHNlZW0gdG8gYmUgdGhlIG9uZXMgaW4gd2hpY2ggdGhlIGdyYWRlcyBhbmQgc2NvcmVzIGltcHJvdmVkLiBUaGUgc3lzdGVtIG9mIHJlaW5zcGVjdGlvbnMgbXVzdCBiZSB3b3JraW5nIQoKTm90ZSB0aGF0IDIwMTcgZGF0YSBtYXBzIGFyZSBvbmx5IGZvciB0aGUgZmlyc3QgcXVhcnRlciBhbmQgYXJlIGxpa2VseSB0byBjaGFuZ2UuCgoKYGBge3J9CiMjIyBEQVRBIEdFTkVSQVRJT04KbGlicmFyeShzdHJpbmdyKQpyZWluc3BlY3QgPC0gVmlvbGF0aW9uc0RhdGEgJT4lIHNlbGVjdCgtQ0FNSVMsIC1EQkEsIC1CVUlMRElORywgLVNUUkVFVCwgLUNVSVNJTkUuREVTQ1JJUFRJT04sIC1CT1JPLCAtU0NPUkUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtUEhPTkUsIElOU1BFQ1RJT04uREFURSwgLUFDVElPTiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtVklPTEFUSU9OLkNPREUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLUNSSVRJQ0FMLkZMQUcsIC1HUkFERSwgLUdSQURFLkRBVEUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLVJFQ09SRC5EQVRFKQpyZWluc3BlY3QkeWVhcjwtZmFjdG9yKGFzLm51bWVyaWMoIGZvcm1hdChyZWluc3BlY3QkSU5TUEVDVElPTi5EQVRFICwgJyVZJykpKQoKI2luc3BlY3Rpb25fdHlwZXMgPC0gSU5TUEVDVElPTi5UWVBFCnJlaW5zcGVjdCA8LSByZWluc3BlY3QgJT4lc2VsZWN0KC1JTlNQRUNUSU9OLkRBVEUpICAlPiUgbmEub21pdCAlPiUgZmlsdGVyKHN0cl9kZXRlY3QoSU5TUEVDVElPTi5UWVBFLCAiUmUtaW5zcGVjdGlvbiIpKSU+JWdyb3VwX2J5KFZJT0xBVElPTi5ERVNDUklQVElPTiwgeWVhciwgWklQQ09ERSApICAlPiUgdGFsbHkjIHN1bW1hcmlzZShyZSA9IHN1bShzdHJfY291bnQocmVpbnNwZWN0JElOU1BFQ1RJT04uVFlQRSwgIlJlLWluc3BlY3Rpb24iKSkpCmBgYAoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdBdmdSZWluc3BlY3QyMDEzLnBuZycpCmBgYAoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdBdmdSZWluc3BlY3QyMDE0LnBuZycpCmBgYAoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdBdmdSZWluc3BlY3QyMDE1LnBuZycpCmBgYAoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdBdmdSZWluc3BlY3QyMDE2LnBuZycpCmBgYAoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdBdmdSZWluc3BlY3QyMDE3LnBuZycpCmBgYAoKIyMgU1RBUkJVQ0tTIE9SIERVTktJTiBET05VVFM/Pz8KYGBge3IsIGVjaG8gPSBGQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoJ3N2c2QuanBnJykKYGBgCgpBcyBhIGxhc3QgYml0IG9mIGFuYWx5c2lzLCB3ZSBkZWNpZGVkIHRvIGhlbHAgc2V0dGxlIGEgaHVnZSBkaWxlbW5hIDogV2hlcmUgc2hvdWxkIHlvdSBnZXQgeW91ciBjb2ZmZWUsIFN0YXJidWNrcyBvciBEdW5raW4gRG9udXRzPwoKYGBge3J9CiMjREFUQSBHRU5FUkFUSU9OIENPREUKbGlicmFyeShzdHJpbmdyKQpDaGFpbnNzRGY8LVZpb2xhdGlvbnNEYXRhJT4lIG11dGF0ZShpc1N0YXJidWNrcz1zdHJfZGV0ZWN0KERCQSwiU1RBUkJVQ0siKSkgJT4lIG11dGF0ZShpc0R1bmtpbj1zdHJfZGV0ZWN0KERCQSwiRFVOS0lOIikpCkNoYWluc3NEZiREQkFbQ2hhaW5zc0RmJGlzU3RhcmJ1Y2tzPT1UUlVFXTwtIlNUQVJCVUNLUyIKQ2hhaW5zc0RmJERCQVtDaGFpbnNzRGYkaXNEdW5raW49PVRSVUVdPC0iRFVOS0lOIgpDaGFpbnNzRGY8LUNoYWluc3NEZiU+JWZpbHRlcihEQkEgJWluJSBjKCJTVEFSQlVDS1MiLCJEVU5LSU4iKSkKQ2hhaW5zc0RmPC1DaGFpbnNzRGYlPiVzZWxlY3QoREJBLEJPUk8sSU5TUEVDVElPTi5EQVRFLFpJUENPREUsU0NPUkUsVklPTEFUSU9OLkRFU0NSSVBUSU9OLEdSQURFKQpDaGFpbnNzRGYkeWVhcjwtZmFjdG9yKGFzLm51bWVyaWMoIGZvcm1hdChDaGFpbnNzRGYkSU5TUEVDVElPTi5EQVRFICwgJyVZJykpKQpDaGFpbnNzRGY8LUNoYWluc3NEZiU+JXNlbGVjdCgtSU5TUEVDVElPTi5EQVRFKQpgYGAKCkFzIHlvdSBjYW4gc2VlLCBEdW5raW4gRG9udXRzIGhhcyBtb3JlIGNyaXRpY2FsIHZpb2xhdGlvbnMgdGhhbiBTdGFyYnVja3MgaW4gZXZlcnkgQm9yb3VnaC4gTm90aWNpYmxlIGRpZmZlcmVuY2VzIGFyZSBlc3BlY2lhbGx5IHNlZSBpbiBCcm9va2x5biBhbmQgTWFuaGF0dGFuLgoKYGBge3J9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCdEUzEucG5nJykKYGBgCgoKQm90aCBlc3RhYmxpc2htZW50cyBzaGFyZWQgdGhlIHNhbWUgdG9wIHZpb2xhdGlvbnMuIExldCdzIHNlZSB3aGF0IHBlcmNlbnRhZ2Ugb2YgdGhlc2UgdmlvbGF0aW9ucyBlYWNoIGhhcy4gCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygnRFMyLnBuZycpCmBgYApPbmNlIGFnYWluLCBTdGFyYnVja3MgaXMgdGhlIHdpbm5lciEKCldoYXQgYWJvdXQgdGhlIGF2ZXJhZ2Ugc2NvcmU/CmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygnRFMzLnBuZycpCmBgYAoKUmVnYXJkbGVzIG9mIHRoZSB5ZWFyLCB0aGUgYXZlcmFnZSBzY29yZSBvZiBEdW5raW4gRG9udXRzIGhhcyBiZWVuIGhpZ2hlci4gVGhlIGhpZ2hlciB0aGUgc2NvcmUsIHRoZSB3b3JzZS4gTm90aWNpYWJsZSBkaWZmZXJlbmNlcyBhcmUgc2VlbiBpbiAyMDE1IGFuZCB0aGUgZmlyc3QgdGhyZWUgbW9udGhzIG9mIDIwMTcuCgpMZXQncyBzZWUgaWYgdGhlIEJvcm91Z2ggeW91J3JlIGluIHNob3VsZCBpbXBhY3QgeW91ciBjaG9pY2UuCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygnRFM0LnBuZycpCmBgYAoKIyMjIFdlIGd1ZXNzIHdoZW4geW91J3JlIGluIEJyb29rbHluLCBpdCBkb2Vzbid0IG1hdHRlciBidXQgZXZlcnl3aGVyZSBlbHNlLCBzdGljayB0byBTdGFyYnVja3MsIGVzcGVjaWFsbHkgeW91IFN0YXRlbiBJc2xhbmQgZm9sa3MhCgoKYGBge3IsIGVjaG8gPSBGQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoJ3dpbm5lci5qcGcnKQpgYGAKCiMjIENvbmNsdXNpb24KTWFueSBsaW1pdGF0aW9ucyBpbmNsdWRlZCB0aGUgbWlzc2luZyBkYXRhIGl0c2VsZi4gV2UgbGVhcm5lZCB0aGF0IHNvbWV0aW1lcywgd2hhdCBtYXkgc2VlbSBsaWtlIGFuIGludHVpdGl2ZSBoeXBvdGhlc2lzLCBpcyBhY3R1YWxseSBjb21wbGV0ZWx5IGZhbHNlLiBUaGVyZSB3ZXJlIG5vdCBhcyBtYW55IHBhdHRlcm5zIGFzIHdlIGFudGljaXBhdGVkIGluIHRlcm1zIG9mIEJvcm91Z2guIFRoZSBvbmx5IHRoaW5nIHdlIGRpZCBzZWUgaXMgdGhhdCBvdmVyIHRpbWUsIHRoZSBzY29yZSBhbmQgZ3JhZGUgaGFzIGluY3JlYXNlZCwgc2hvd2luZyB1cyB0aGF0IHRoZSBpbnNwZWN0aW9ucyBzZWVtIHRvIGJlIHdvcmtpbmcgYW5kIHRoYXQgcmVzdHVhcmFudHMgYXJlIGltcHJvdmluZyB0aGVpciBmYWNpbGl0aWVzIHRvIHJlY2VpZXZlIGEgYmV0dGVyIHNjb3JlIGFuZCBncmFkZS4gQWNjb3JkaW5nIHRvIHRoZSB3ZWJzaXRlIHdlIHJlY2VpdmVkIHRoZSBkYXRhIGZyb20sIHRocmVlIGZpZWxkcyBhcmUgc29vbiB0byBiZSBhZGRlZCBhcyBkYXRhIGxvZ2ljIGJlY29tZXMgYXZhaWxhYmxlIHRvIHBvcHVsYXRlIHRoZW0gYWNjdXJhdGVseS4gVGhvc2UgZmllbGRzIGFyZSBWSU9MQVRJT04gUE9JTlRTICh0aGUgcG9pbnRzIGFzc2lnbmVkIHRvIGEgdmlvbGF0aW9uIGJlZm9yZSBvciBhZnRlciBhZGp1ZGljYXRpb24sIGRlcGVuZGluZyBvbiB3aGV0aGVyIGFkanVkaWNhdGlvbiBoYXMgb2NjdXJyZWQpLCBGSU5FUyBUT1RBTCAodGhlIGZpbmUgYW1vdW50IGFmdGVyIGFkanVkaWNhdGlvbiksIGFuZCBERUNJU0lPTiBEQVRFIChhZGp1ZGljYXRpb24gZGF0ZSDigJMgb3IgZGF0ZSBhIGdyYWRlIGJlY29tZXMgZmluYWwpLiBXaXRoIHRoaXMgaW5mb3JtYXRpb24sIHdlIGNhbiBkbyBtb3JlIGFuYWx5c2lzIHRvIHNlZSB0aGUgZmluZXMgb2YgdmFyaW91cyB2aW9sYXRpb25zIGFuZCBsZWFybiBob3cgZWFjaCB2aW9sYXRpb24gYWN0dWFsbHkgYWZmZWN0cyB0aGUgc2NvcmUuIFdpdGggdGhhdCwgcmVzdGF1cmFudHMgY2FuIGZvY3VzIG9uIHRoZSBtYWluIHZpb2xhdGlvbnMgdG8gaW1wcm92ZSB0aGVpciBzY29yZXMgaW4gdGhlIG5leHQgaW5zcGVjdGlvbi4KCgoKCgo=